From eccefd40d57a5e60684ed4f4e110502d66fd2a2e Mon Sep 17 00:00:00 2001 From: Fadi George Date: Mon, 12 Oct 2026 18:34:36 -0700 Subject: [PATCH 1/4] feat(appium): [SDK-5533] add log view spec and JS event assertions Specify a sticky log view for all demos and assert JS listener events through it. checkNotification now expects one foregroundWillDisplay and one click entry per notification, and Android taps the notification so the click fires on both platforms. Demos without a log view skip the assertions. Co-Authored-By: Claude Opus 5.5 --- appium/tests/helpers/app.ts | 56 ++++++++++++++++++++++++++++++++++++- demo/build.md | 38 +++++++++++++++++++++++++ demo/styles.md | 17 +++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) diff --git a/appium/tests/helpers/app.ts b/appium/tests/helpers/app.ts index 60bb404..f997a5f 100644 --- a/appium/tests/helpers/app.ts +++ b/appium/tests/helpers/app.ts @@ -322,6 +322,46 @@ export async function returnToApp() { await driver.pause(1_000); } +export async function hasLogView(): Promise { + 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 { + 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()); +} + +/** Number of log entries that contain every one of `parts`. */ +export async function countLogMessages(...parts: string[]): Promise { + 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 @@ -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(); } @@ -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() { diff --git a/demo/build.md b/demo/build.md index 3e2166e..b3b3de2 100644 --- a/demo/build.md +++ b/demo/build.md @@ -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 @@ -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) @@ -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). @@ -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 @@ -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 | diff --git a/demo/styles.md b/demo/styles.md index a60e83f..70559ca 100644 --- a/demo/styles.md +++ b/demo/styles.md @@ -200,6 +200,23 @@ 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 | `#1E1E1E` | +| Entry text | `#D4D4D4`, monospace, 10 | +| Timestamp, empty state | osGrey600 | +| Header text | `LOGS ({count})`, osGrey500, monospace, 11, bold, 0.5 letter spacing | +| Header icons | osGrey500, 18; trash (filled) then chevron, 12 apart | +| Header padding | 4 vertical, 16 horizontal | +| Body height | 100 (fixed; scrolls internally) | +| 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. From 005e3616986b8cdd564b0aee858d3f631afcaaf1 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Mon, 12 Oct 2026 18:36:26 -0700 Subject: [PATCH 2/4] docs(demo): [SDK-5533] add log view color tokens Co-Authored-By: Claude Opus 5.5 --- demo/styles.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/demo/styles.md b/demo/styles.md index 70559ca..91d7560 100644 --- a/demo/styles.md +++ b/demo/styles.md @@ -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 @@ -206,8 +208,8 @@ Sticky panel below the header bar (see build.md Prompt 2.0). Dark, logcat-style, | Property | Value | | ---------------- | ------------------------------------------------------------ | -| Background | `#1E1E1E` | -| Entry text | `#D4D4D4`, monospace, 10 | +| Background | osLogBackground (`#1E1E1E`) | +| Entry text | osLogText (`#D4D4D4`), monospace, 10 | | Timestamp, empty state | osGrey600 | | Header text | `LOGS ({count})`, osGrey500, monospace, 11, bold, 0.5 letter spacing | | Header icons | osGrey500, 18; trash (filled) then chevron, 12 apart | From 67173bc6198e655113a822346d2f0b52a0549b09 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Tue, 13 Oct 2026 10:54:40 -0700 Subject: [PATCH 3/4] docs(demo): update log view spec dimensions --- demo/styles.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/demo/styles.md b/demo/styles.md index 91d7560..ee7b53e 100644 --- a/demo/styles.md +++ b/demo/styles.md @@ -209,12 +209,14 @@ Sticky panel below the header bar (see build.md Prompt 2.0). Dark, logcat-style, | Property | Value | | ---------------- | ------------------------------------------------------------ | | Background | osLogBackground (`#1E1E1E`) | -| Entry text | osLogText (`#D4D4D4`), monospace, 10 | +| 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, 18; trash (filled) then chevron, 12 apart | -| Header padding | 4 vertical, 16 horizontal | +| 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 | From b077ae9ee84db90d8c9bfe92a3cffe2e6d6ca223 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Tue, 13 Oct 2026 13:02:39 -0700 Subject: [PATCH 4/4] docs(demo): size log clear icon at 14 Co-Authored-By: Claude Opus 5.5 --- demo/styles.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/styles.md b/demo/styles.md index ee7b53e..d1244cf 100644 --- a/demo/styles.md +++ b/demo/styles.md @@ -212,7 +212,7 @@ Sticky panel below the header bar (see build.md Prompt 2.0). Dark, logcat-style, | 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, 18; trash (filled) then chevron, 12 apart | +| 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) |