Add resilient UI helpers for third-party / adversarial SPAs.
CI / release (pull_request) Has been skipped
CI / skip-ci-check (pull_request) Successful in 5s
CI / secret-scan (pull_request) Successful in 4s
CI / build-and-test (pull_request) Successful in 23s
CI / selftest (pull_request) Successful in 15s

Generalize patterns from LinkedIn automation: aria-label regex locators,
dialog reopen-retry, contenteditable typing, persistent flag-file sessions,
and a default noise exclude preset for NetworkErrorMonitor.
This commit is contained in:
2026-07-15 18:17:02 -04:00
parent ee7b130db0
commit 8b30ab5a6d
15 changed files with 784 additions and 1 deletions
+57
View File
@@ -85,6 +85,10 @@ test('sign-out stays on public host', async ({ page, playkitConfig, timings }) =
| `saveStorageState` / `storageStateUse` | Auth once, reuse across specs |
| `playkitFailureArtifacts` | Trace/video/screenshot only on failure |
| `interceptNetworkCall` | Spy or mock the next matching page network call |
| `byAriaLabel` / `clickByAriaLabel` | Find/click by a regex over `aria-label`, scoped to a page or a narrower locator |
| `withDialog` | Retry an action against a modal that might have silently closed, reopening it first |
| `fillContentEditable` | Type into `contenteditable` rich-text fields with real paragraph breaks |
| `runPersistentSession` | Keep one browser session open across runs via flag files instead of relaunching/re-authenticating |
| `playkit` CLI | `init` scaffold + `smoke` post-deploy gate |
See also `docs/NETWORK.md`, `docs/SELFTEST.md`, `docs/NPM_REGISTRY.md`, `docs/OPS.md`, `docs/IDEAS.md`, `docs/OUTLINE.md`.
@@ -105,6 +109,59 @@ try {
}
```
## Resilient UI automation (third-party / adversarial SPAs)
`click`/`fill`/`waitForVisible` above assume you already have a correct
`Locator` — great when you control the markup and have stable test ids. These
helpers are for the opposite case: driving a third-party site you don't
control, where selectors are dynamic/compound (aria-labels that embed
record-specific text), rich-text fields aren't real `<textarea>`s, modals
close out from under you, and the site has its own bot-detection you'd rather
not retrigger by relaunching the browser on every run.
```ts
import {
byAriaLabel,
clickByAriaLabel,
withDialog,
fillContentEditable,
runPersistentSession,
} from '@levkin/playkit';
// aria-label is often compound + record-specific ("Edit Staff Automation
// Engineer at NiyaSoft") — a regex survives per-record text variation better
// than an exact string copied from one DOM dump.
await clickByAriaLabel(page, /Edit.*at NiyaSoft/i);
// Re-open the dialog and retry if it closed underneath you mid-flow.
await withDialog(
{
isOpen: () => page.getByRole('button', { name: 'Save' }).isVisible(),
reopen: () => clickByAriaLabel(page, /Edit.*at NiyaSoft/i),
},
async () => {
await fillContentEditable(page.locator('[contenteditable="true"]').first(), longBioText);
await page.getByRole('button', { name: 'Save' }).click();
},
);
// Keep ONE browser open across many runs — touch RUN/READY/CLOSE flag files
// instead of relaunching (and re-triggering captchas) each time.
await runPersistentSession({
dir: '.session',
storageStatePath: '.session/state.json',
launch: async () => {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();
return { browser, context, page };
},
onRun: async ({ page }) => {
/* your flow against the live page */
},
});
```
## Email testing (Mailpit default, Mailtrap optional)
`createMailInbox()` picks the provider from `PLAYKIT_MAIL_PROVIDER` (default