Skip to content

fix(e2e): observe the dictation banner inside the 6s it exists - #733

Merged
rubenvdlinde merged 1 commit into
developmentfrom
fix/e2e-dictation-banner-window
Aug 31, 2026
Merged

fix(e2e): observe the dictation banner inside the 6s it exists#733
rubenvdlinde merged 1 commit into
developmentfrom
fix/e2e-dictation-banner-window

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

a local-pinned agent does NOT fall back to the browser when the service is down failed intermittently with:

Error: expect(locator).toContainText(expected) failed
Locator: locator('[data-testid="cn-ai-input-dictation-error"]')
Expected pattern: /private/i     Timeout: 15000ms
Error: element(s) not found

The assertion outlived the thing it asserted on

CnAiInput's showDictationError() arms:

this.dictationErrorTimer = setTimeout(() => {
    this.dictationError = ''
}, DICTATION_ERROR_TIMEOUT)   // = 6000

The banner wipes itself after 6 seconds, deliberately — the component's own comment explains why: "a banner with no lifetime is a banner that becomes furniture".

The test allowed 15 seconds to observe it. If the runner stalled between the click and the first poll — plausible under CI contention — the banner had already cleared, and Playwright then waited out the rest of the 15s for an element that no longer existed. The message reads like a missing feature; the feature was there and gone.

Measured on development 2026-08-31: 2 failures in 8 runs, and a push run and a pull_request run of the same commit disagreed — the signature of a missed window, not of an app that failed to refuse.

The fix

Observe inside the banner's own lifetime, and repeat the trigger if the window was missed (bounded at 3).

Re-clicking is safe and changes nothing the test is about: the control is aria-disabled, the app refuses the click either way, each call re-arms the banner's timer, and the assertion that actually matters — that no browser recogniser was constructed — is checked over the whole test rather than per click.

Deliberately not a longer timeout and not a weakened matcher. The refusal message is the point of this test: it still must appear, still must say "private", and a genuine failure to show it still fails here after three honest attempts. This test exists to prove confidential audio never reaches Google, and nothing about that guarantee is relaxed.

Timing

banner TTL after each click 6s
waitFor(visible) 4s — starts at the click, inside the TTL
worst case resolves at 4s, leaving 2s of banner for the text assertion
3 attempts ~12s, inside the spec's budget

`a local-pinned agent does NOT fall back to the browser when the service is
down` failed intermittently with

  Error: expect(locator).toContainText(expected) failed
  Locator: locator('[data-testid="cn-ai-input-dictation-error"]')
  Expected pattern: /private/i     Timeout: 15000ms
  Error: element(s) not found

The assertion outlived the thing it asserted on. CnAiInput's
`showDictationError()` arms `setTimeout(..., DICTATION_ERROR_TIMEOUT)` with
DICTATION_ERROR_TIMEOUT = 6000 -- the banner wipes itself after 6s,
deliberately, because "a banner with no lifetime becomes furniture". The test
allowed 15s to observe it. If the runner stalled between the click and the
first poll the banner had already cleared, and Playwright then waited out the
rest of the 15s for an element that no longer existed.

Measured on development 2026-08-31: 2 failures in 8 runs, and a `push` and a
`pull_request` run of the SAME commit disagreed -- the signature of a missed
window rather than of an app that failed to refuse.

So the observation now happens inside the banner's own lifetime, and the
trigger is repeated if the window was missed (bounded at 3). Re-clicking is
safe and changes nothing the test is about: the control is `aria-disabled`,
the app refuses the click either way, each call re-arms the banner's timer,
and the assertion that matters -- that NO browser recogniser was constructed
-- is checked over the whole test rather than per click.

Deliberately NOT a longer timeout and NOT a weakened matcher. The refusal
message is the point of the test: it still must appear, still must say
"private", and a genuine failure to show it still fails here after three
honest attempts.

Worst case: waitFor resolves at 4s leaving 2s of banner for the text
assertion; 3 attempts is ~12s, inside the spec's budget.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/hermiq @ 7f3a423

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-specs
check-manifest
test-l10n
format
check-l10n-js
check-schema-l10n
composer ✅ 123/123
npm ✅ 730/730
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright ⏭️ deferred — runs on the promotion into beta/main, not on a pull request into development
Hydra gates

Quality workflow — 2026-08-31 17:39 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit 0f242f9 into development Aug 31, 2026
51 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant