Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion appium/tests/helpers/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,46 @@ export async function returnToApp() {
await driver.pause(1_000);
}

export async function hasLogView(): Promise<boolean> {
return $(byTestIdSelector('log_view'))
.isExisting()
.catch(() => false);
}

/** Text of every entry in the demo's on-screen log view, oldest first. */
export async function getLogMessages(): Promise<string[]> {
if (isWebViewSDK) {
return driver.execute(() =>
Array.from(document.querySelectorAll('[data-testid^="log_entry_"]')).map(
(el) => el.textContent ?? '',
),
);
}

const selector =
getPlatform() === 'android'
? 'android=new UiSelector().resourceIdMatches(".*log_entry_.*")'
: '-ios predicate string:name BEGINSWITH "log_entry_"';
return $$(selector).map((el) => el.getText());
Comment thread
fadi-george marked this conversation as resolved.
}

/** Number of log entries that contain every one of `parts`. */
export async function countLogMessages(...parts: string[]): Promise<number> {
const messages = await getLogMessages();
return messages.filter((message) => parts.every((part) => message.includes(part))).length;
}

/** Expect exactly one new log entry containing every one of `parts` beyond `baseline`. */
export async function expectOneNewLog(parts: string[], baseline = 0, timeoutMs = 15_000) {
await driver.waitUntil(async () => (await countLogMessages(...parts)) > baseline, {
timeout: timeoutMs,
timeoutMsg: `No new log entry containing ${JSON.stringify(parts)}`,
});
// Give a duplicate replay time to land before counting.
await driver.pause(2_000);
expect(await countLogMessages(...parts)).toBe(baseline + 1);
}

/**
* Expand an Android notification row when OEMs keep it collapsed. The chevron
* lookup is scoped to the ancestor row containing `title`; otherwise a busy
Expand Down Expand Up @@ -394,7 +434,9 @@ export async function waitForNotification(opts: {
await image.waitForDisplayed({ timeout: 5_000 });
}

await returnToApp();
// Tap rather than press back so the click listener fires, matching iOS.
await titleEl.click();
await driver.pause(1_000);
} finally {
await ensureMainWebViewContext();
}
Expand Down Expand Up @@ -460,12 +502,24 @@ export async function checkNotification(opts: {
const button = await scrollToEl(opts.buttonId);
await driver.pause(2_000); // small wait to hopefully get image notif early

const displayLog = ['Notification foregroundWillDisplay:', opts.title];
const clickLog = ['Notification click:', opts.title];
const logged = await hasLogView();
const [displaysBefore, clicksBefore] = logged
? await Promise.all([countLogMessages(...displayLog), countLogMessages(...clickLog)])
: [0, 0];

await button.click();
await waitForNotification({
title: opts.title,
body: opts.body,
expectImage: opts.expectImage,
});

// Demos without the log view can't report JS events yet.
if (!logged) return;
await expectOneNewLog(displayLog, displaysBefore);
await expectOneNewLog(clickLog, clicksBefore);
}

export async function isWebViewVisible() {
Expand Down
38 changes: 38 additions & 0 deletions demo/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,19 @@ OneSignal.LiveActivities.setupDefault({
- InAppMessages: willDisplay, didDisplay, willDismiss, didDismiss, click
- Notifications: click, foregroundWillDisplay

Every listener in steps 2 and 3 writes one entry to the Log View (Prompt 2.0) and the console. Use these messages exactly; the shared Appium suite matches on them:

| Event | Log message |
| ------------------------------- | ------------------------------------------------------------------------------------ |
| Notification click | `Notification click: {notificationId} {title}` |
| Notification foregroundWillDisplay | `Notification foregroundWillDisplay: {notificationId} {title}` |
| IAM willDisplay / didDisplay / willDismiss / didDismiss / click | `IAM {event}: {messageId}` (e.g. `IAM didDisplay: {messageId}`) |
| Push subscription change | `Push subscription changed: id={prev} → {curr}, optedIn={prev} → {curr}, token={prev8}… → {curr8}…` |
| Notification permission change | `Permission changed: {granted}` |
| User state change | `User changed: onesignalId={id}, externalId={externalId}` |

Use `null` for missing ids and an empty string for a missing title. Truncate push tokens to their first 8 characters.

**3. Register state-layer observers.** Wire these in the state container so they can mutate UI state:

- Push subscription change
Expand All @@ -137,6 +150,7 @@ Clean up listeners on teardown if the platform requires it.

### Section Order (top to bottom)

0. **Log View** (sticky under the app bar, outside the scroll view; see Prompt 2.0)
1. **App Section** (App ID, Guidance Banner, Consent Toggle)
2. **User Section** (Status, External ID, Login/Logout)
3. **Push Section** (Push ID, Enabled Toggle, Auto-prompts on load)
Expand All @@ -154,6 +168,20 @@ Clean up listeners on teardown if the platform requires it.
15. **Live Activities Section** (iOS only - Start, Update, End)
16. **Next Screen Button**

### Prompt 2.0 - Log View

A logcat-style panel pinned directly below the app bar. It stays visible while the main scroll view scrolls underneath it.

1. Header row: `LOGS ({count})` on the left; trash icon (clear) and chevron (collapse/expand) on the right.
2. Body: fixed-height list of entries, oldest first, each `{HH:mm:ss} {message}` with the time de-emphasized. Auto-scroll to the newest entry when one is added or the panel is expanded. Long entries wrap.
3. Empty state: `No logs yet`.
4. Collapse hides the body only; the header row stays. Starts expanded on every launch. Entries stay in the view hierarchy while collapsed.
5. Clear removes all entries.
6. Keep at most 100 entries in memory (drop the oldest). Never persist them: every launch, including a cold start from a notification tap, starts empty so the suite can assert per-launch event counts.
7. Expose a single `log(message)` helper that appends an entry and writes `[OneSignal] {message}` to the console. All SDK listeners (Prompt 1.5) go through it.

Styling in styles.md (Log View).

### Prompt 2.1a - App Section

1. App ID display (readonly text).
Expand Down Expand Up @@ -405,6 +433,7 @@ This ensures SDK is configured before init, and UI reflects SDK's actual state.

### Not Persisted (In-Memory Only)

- **Log View entries**: session-only, cleared on restart
- **triggersList**: session-only, cleared on restart
- **aliasesList**: fetched from API on start/login, local adds are immediate (SDK syncs async)
- **emailsList, smsNumbersList**: fetched from API each session
Expand Down Expand Up @@ -449,6 +478,15 @@ Use the platform's native accessibility/test ID mechanism. The ids MUST match ex

Section keys: `app`, `user`, `push`, `send_push`, `iam`, `send_iam`, `aliases`, `emails`, `sms`, `tags`, `outcomes`, `triggers`, `custom_events`, `location`, `live_activities`

**Log View**:

| Identifier | Element |
| ------------------- | ----------------------------------------------------------- |
| `log_view` | Log View container |
| `log_entry_{index}` | Log entry text, 0-based, oldest first |
| `log_clear_button` | Trash (clear) button; accessibility label `Clear logs` |
| `log_toggle_button` | Collapse/expand chevron; label `Collapse logs` / `Expand logs` |

**Value displays**:

| Identifier | Element |
Expand Down
21 changes: 21 additions & 0 deletions demo/styles.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Design spec for OneSignal SDK demo/sample apps. Values use platform-independent
| osDivider | `#E8EAED` | Dividers |
| osWarningBackground | `#FFF8E1` | Guidance / warning banner card |
| osBackdrop | `rgba(0,0,0,0.54)` | Dialog / overlay backdrop |
| osLogBackground | `#1E1E1E` | Log View background |
| osLogText | `#D4D4D4` | Log View entry text |

## Spacing

Expand Down Expand Up @@ -200,6 +202,25 @@ When a list exceeds `maxVisible` items (default 5), the overflow is hidden behin

- Bottom padding: 24

## Log View

Sticky panel below the header bar (see build.md Prompt 2.0). Dark, logcat-style, independent of light/dark app theme.

| Property | Value |
| ---------------- | ------------------------------------------------------------ |
| Background | osLogBackground (`#1E1E1E`) |
| Entry text | osLogText (`#D4D4D4`), monospace, 8 |
| Timestamp, empty state | osGrey600 |
| Header text | `LOGS ({count})`, osGrey500, monospace, 11, bold, 0.5 letter spacing |
| Header icons | osGrey500; trash (filled) 14, then chevron 18, 12 apart |
| Header height | 24 (fixed; content vertically centered) |
| Header padding | 0 vertical, 16 horizontal |
| Body height | 100 (fixed; scrolls internally) |
| Total height | 24 collapsed, 124 expanded |
| Body padding | 0 top, 16 horizontal, 6 bottom |
| Entry line height | 1.4 |
| Stacking | Above page content, below the header bar |

## Loading State

Inline placeholder shown inside a list card while remote data is being fetched (e.g. aliases, tags, emails, SMS after login or cold start). Occupies the same slot as the empty-state message, so the layout does not shift when data arrives.
Expand Down
Loading