@owlmeans/test-ui 0.1.18-rc.50 → 0.1.18-rc.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/testing-ui/SKILL.md +25 -15
- package/build/consent/types.d.ts +36 -0
- package/build/consent/types.d.ts.map +1 -0
- package/build/consent/types.js +2 -0
- package/build/consent/types.js.map +1 -0
- package/build/consent.d.ts +4 -0
- package/build/consent.d.ts.map +1 -0
- package/build/consent.js +41 -0
- package/build/consent.js.map +1 -0
- package/build/consts.local.d.ts +6 -0
- package/build/consts.local.d.ts.map +1 -1
- package/build/consts.local.js +6 -0
- package/build/consts.local.js.map +1 -1
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/mount.d.ts.map +1 -1
- package/build/mount.js +4 -0
- package/build/mount.js.map +1 -1
- package/build/page/types.d.ts +7 -0
- package/build/page/types.d.ts.map +1 -1
- package/build/page.d.ts.map +1 -1
- package/build/page.js +10 -2
- package/build/page.js.map +1 -1
- package/build/types.d.ts +6 -0
- package/build/types.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/consent/types.ts +38 -0
- package/src/consent.ts +48 -0
- package/src/consts.local.ts +9 -0
- package/src/index.ts +2 -0
- package/src/mount.ts +4 -0
- package/src/page/types.ts +7 -0
- package/src/page.ts +12 -2
- package/src/types.ts +6 -0
package/README.md
CHANGED
|
@@ -9,7 +9,8 @@ These are **not** end-to-end UI tests — they exercise individual components in
|
|
|
9
9
|
- `browserHelper.launchBrowser(opts?)` — return the shared chromium instance for this `bun test` process. Idempotent.
|
|
10
10
|
- `browserHelper.closeBrowser()` — tear it down. Call from `afterAll`.
|
|
11
11
|
- `browserHelper.withPage(fn)` — convenience: lease a fresh context+page for the duration of `fn`, then dispose the context.
|
|
12
|
-
- `mountComponent({ url, component?, props? })` — opens a fresh context, navigates to a harness URL with the component / props encoded, returns `{ page, close }` so the spec can assert against `page.locator(...)` and dispose the context when done.
|
|
12
|
+
- `mountComponent({ url, component?, props?, consentGeo? })` — opens a fresh context, navigates to a harness URL with the component / props encoded, returns `{ page, close }` so the spec can assert against `page.locator(...)` and dispose the context when done. `consentGeo` pins where the visitor is before the first navigation.
|
|
13
|
+
- `consentGeoTestHelper.mockConsentGeo(pageOrContext, geo)` / `.traceCalls(page)` — answer the page's same-origin `/cdn-cgi/trace` from an init script (`'US'`, or `{ country?, fail?, delayMs?, gpc? }`), so a consent test does not depend on the runner's own country; count the trace requests.
|
|
13
14
|
- `makePageHelper(page)` — drives one page of an OwlMeans app: `.acceptConsent(opts?)`, `.answerMarketingConsent(opts?)`, `.loginViaDispatcher(baseUrl, token, opts?)`, `.loginViaSupervisorForm(opts)`, `.saveScreenshot(dir, name)`.
|
|
14
15
|
- `supervisorAuthHelper` — bearers without a browser: `.pregenerateAuthToken(opts)` (signed offline) and `.authenticateViaSupervisorApi(opts)` (the live supervisor flow over the API).
|
|
15
16
|
- `Browser`, `BrowserContext`, `Page`, `Locator` — re-exports of the Playwright types so consumers don't need a direct `playwright` import.
|
|
@@ -60,7 +61,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
60
61
|
your project's skill store (`.agents/skills/`):
|
|
61
62
|
|
|
62
63
|
```sh
|
|
63
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
64
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.54
|
|
64
65
|
```
|
|
65
66
|
|
|
66
67
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/test-ui",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-10-
|
|
4
|
+
"version": "0.1.18-rc.52",
|
|
5
|
+
"generatedAt": "2026-10-15T10:20:01.289Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -6,7 +6,7 @@ description: Category-D component-level acceptance tests for OwlMeans Common UI
|
|
|
6
6
|
|
|
7
7
|
# UI Acceptance Tests — Category D (bun test + Playwright as a library)
|
|
8
8
|
|
|
9
|
-
**Install:** `"@owlmeans/test-ui": "^0.1.18-rc.
|
|
9
|
+
**Install:** `"@owlmeans/test-ui": "^0.1.18-rc.52"` in `devDependencies`
|
|
10
10
|
|
|
11
11
|
`@owlmeans/test-ui` depends on `playwright`, so the browser library arrives with it; add
|
|
12
12
|
`playwright` to `devDependencies` as well when a spec imports a launcher itself. The Vite harness
|
|
@@ -57,7 +57,8 @@ Downloads the chromium binary the `playwright` library drives. CI installs it in
|
|
|
57
57
|
| `browserHelper.withPage(fn)` | Lease a fresh context+page for the duration of `fn`, dispose context on completion. |
|
|
58
58
|
| `mountComponent(opts)` | Open a fresh context, navigate to `opts.url`, return `Mounted` = `{ page, close }`. `component` and `props`, when given, are appended as `?component=` and `?props=`; omit them when the harness mounts a fixed root, which is what every harness here does. |
|
|
59
59
|
| `MountOptions` | `{ url, component?, props?, waitUntil?, timeout? }` — `waitUntil` defaults to `domcontentloaded`, see below. |
|
|
60
|
-
| `makePageHelper(page).acceptConsent({ timeout? })` | Wait up to `timeout` (default 5s) for `[data-consent-dialog]
|
|
60
|
+
| `makePageHelper(page).acceptConsent({ timeout? })` | Wait up to `timeout` (default 5s) for the consent store to SETTLE — `[data-consent-dialog]` up (bar or window), or `<html data-consent="decided">` — then accept all and wait for it to detach. Returns whether it answered one; a decision made without UI returns `false` at once. |
|
|
61
|
+
| `consentGeoTestHelper.mockConsentGeo(pageOrContext, geo)` / `.traceCalls(page)` | Pin where the visitor is: an init script answering the same-origin `/cdn-cgi/trace` (`'US'`, or `{ country?, fail?: true \| 'hang', delayMs?, gpc? }`), installed for the NEXT navigation; counts the calls in the page. `mountComponent({ …, consentGeo })` installs it before the first one. |
|
|
61
62
|
| `makePageHelper(page).saveScreenshot(dir, name)` | Full-page PNG to `<dir>/<name>.png`, creating `dir`. Returns the absolute path. |
|
|
62
63
|
| Re-exports: `Browser`, `BrowserContext`, `Page`, `Locator` | Playwright types — no direct `playwright` import needed. |
|
|
63
64
|
|
|
@@ -290,7 +291,8 @@ the filled form just before submit.
|
|
|
290
291
|
## The consent dialog blocks the login form, and the failure blames the button
|
|
291
292
|
|
|
292
293
|
An OwlMeans app asks for cookie consent **before** it will start an authentication flow, and the
|
|
293
|
-
|
|
294
|
+
surface — the bar on its transparent overlay, or the preferences window — offers no dismissal: a
|
|
295
|
+
decision is what the gate is waiting for. Until it is
|
|
294
296
|
answered the overlay intercepts pointer events, so a login form renders, resolves, reports itself
|
|
295
297
|
`visible, enabled and stable`, and still cannot be clicked. Playwright retries for the full timeout
|
|
296
298
|
and then reports `click: Timeout … waiting for getByTestId('supervisor-submit')`, naming the
|
|
@@ -298,18 +300,26 @@ button. Nothing is wrong with the button; read the `subtree intercepts pointer e
|
|
|
298
300
|
names `[data-consent-dialog]`.
|
|
299
301
|
|
|
300
302
|
`makePageHelper(page).loginViaSupervisorForm` therefore calls `.acceptConsent()` after navigating. `.acceptConsent` is
|
|
301
|
-
public for specs that drive their own login: it waits for
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
303
|
+
public for specs that drive their own login: it waits for the consent store to SETTLE — never while
|
|
304
|
+
the visitor is still being located, when the transparent overlay is up and would take the click —
|
|
305
|
+
then clicks `[data-consent-accept-all]` on whichever surface asks, waits for it to detach, and
|
|
306
|
+
reports whether it answered one. It accepts **all** categories deliberately — a spec asserting a
|
|
307
|
+
narrower decision must make that decision itself rather than inherit a silent minimum.
|
|
308
|
+
|
|
309
|
+
**Where the app asks depends on where the runner is.** With the geo gate on, a Cloudflare-proxied
|
|
310
|
+
app asks only visitors located in a consent country — and the trace answers with the RUNNER's
|
|
311
|
+
country. A spec whose assertions need the surface pins it: `mountComponent({ url, consentGeo: 'PL' })`
|
|
312
|
+
or `consentGeoTestHelper.mockConsentGeo(context, 'PL')` before the navigation; a spec about the
|
|
313
|
+
automatic decision pins `'US'`. Never `page.route` for it — interception disables the cache for
|
|
314
|
+
every request, and a vite-served application stalls on its hundreds of unbundled modules.
|
|
315
|
+
|
|
316
|
+
**A `false` is cheap when the app says so, and costs the full wait when it cannot.** An app on a
|
|
317
|
+
consent package that marks `<html data-consent>` settles at once on a stored or automatic
|
|
318
|
+
decision. One older than that marker — or one that ships no consent widget — only ever settles by
|
|
319
|
+
showing a dialog, so a page with none pays the timeout: 5s by `.acceptConsent`'s own default, but
|
|
320
|
+
`.loginViaSupervisorForm` passes its `timeout` straight through and that defaults to `60_000`.
|
|
321
|
+
Drive an app with no widget with `consent: 'ignore'`, which skips the call entirely; that is also
|
|
322
|
+
the switch a spec flips when it wants to answer the dialog itself.
|
|
313
323
|
|
|
314
324
|
## `makePageHelper(page).answerMarketingConsent` — cheap absence detection, unlike the cookie dialog
|
|
315
325
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { BrowserContext, Page } from 'playwright';
|
|
2
|
+
/**
|
|
3
|
+
* A fake answer from Cloudflare's trace endpoint: a country code (`'US'`), or
|
|
4
|
+
* `{ country?, fail?, delayMs?, gpc? }`.
|
|
5
|
+
*/
|
|
6
|
+
export type ConsentGeoMock = string | ConsentGeoMockOptions;
|
|
7
|
+
export interface ConsentGeoMockOptions {
|
|
8
|
+
/** The `loc=` the fake trace answers with. Defaults to `PL` — a consent country. */
|
|
9
|
+
country?: string;
|
|
10
|
+
/** `true` answers 404 (the host is not behind Cloudflare); `'hang'` never answers. */
|
|
11
|
+
fail?: boolean | 'hang';
|
|
12
|
+
/** Answer only after this many ms — long enough to see the spinner. */
|
|
13
|
+
delayMs?: number;
|
|
14
|
+
/** Send Global Privacy Control (`navigator.globalPrivacyControl`). */
|
|
15
|
+
gpc?: boolean;
|
|
16
|
+
/** The trace path the page asks. Defaults to `/cdn-cgi/trace`. */
|
|
17
|
+
path?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Pinning where a visitor is, so a consent test does not depend on where its runner happens to be:
|
|
21
|
+
* every app behind Cloudflare answers the trace with the RUNNER's country.
|
|
22
|
+
*/
|
|
23
|
+
export interface ConsentGeoTestHelper {
|
|
24
|
+
/**
|
|
25
|
+
* Answer the page's same-origin trace request from inside the page: an init script that wraps
|
|
26
|
+
* `window.fetch` before any page script runs, and counts the calls in
|
|
27
|
+
* `window.__consentTraceCalls`. Never `page.route` — interception disables the cache for every
|
|
28
|
+
* request, and a vite-served application stalls on its hundreds of unbundled modules.
|
|
29
|
+
*
|
|
30
|
+
* Applies to the NEXT navigation of the page or context it is installed on.
|
|
31
|
+
*/
|
|
32
|
+
mockConsentGeo: (target: Page | BrowserContext, geo: ConsentGeoMock) => Promise<void>;
|
|
33
|
+
/** How many trace requests the page made since it loaded (`mockConsentGeo` only). */
|
|
34
|
+
traceCalls: (page: Page) => Promise<number>;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/consent/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEtD;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,qBAAqB,CAAA;AAE3D,MAAM,WAAW,qBAAqB;IACpC,oFAAoF;IACpF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sFAAsF;IACtF,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IACvB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sEAAsE;IACtE,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;;;OAOG;IACH,cAAc,EAAE,CAAC,MAAM,EAAE,IAAI,GAAG,cAAc,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACrF,qFAAqF;IACrF,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC5C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/consent/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent.d.ts","sourceRoot":"","sources":["../src/consent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAyC,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAErG,eAAO,MAAM,0BAA0B,QAAO,oBAyC7C,CAAA;AAED,eAAO,MAAM,oBAAoB,sBAA+B,CAAA"}
|
package/build/consent.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { DEFAULT_CONSENT_GEO_COUNTRY, DEFAULT_CONSENT_TRACE_PATH } from './consts.local.js';
|
|
2
|
+
export const createConsentGeoTestHelper = () => {
|
|
3
|
+
const mockConsentGeo = async (target, geo) => {
|
|
4
|
+
const options = {
|
|
5
|
+
country: typeof geo === 'string' ? geo : geo.country ?? DEFAULT_CONSENT_GEO_COUNTRY,
|
|
6
|
+
delayMs: typeof geo === 'string' ? 0 : geo.delayMs ?? 0,
|
|
7
|
+
path: typeof geo === 'string' ? DEFAULT_CONSENT_TRACE_PATH : geo.path ?? DEFAULT_CONSENT_TRACE_PATH,
|
|
8
|
+
...(typeof geo !== 'string' && geo.fail != null ? { fail: geo.fail } : {}),
|
|
9
|
+
...(typeof geo !== 'string' && geo.gpc != null ? { gpc: geo.gpc } : {}),
|
|
10
|
+
};
|
|
11
|
+
// A string, not a function: it runs in the page, and this package compiles without DOM types.
|
|
12
|
+
await target.addInitScript({ content: `(function(arg){
|
|
13
|
+
if (arg.gpc != null) {
|
|
14
|
+
Object.defineProperty(Navigator.prototype, 'globalPrivacyControl', { get: function () { return arg.gpc }, configurable: true })
|
|
15
|
+
}
|
|
16
|
+
window.__consentTraceCalls = 0
|
|
17
|
+
var original = window.fetch.bind(window)
|
|
18
|
+
window.fetch = function (input, init) {
|
|
19
|
+
var href = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url
|
|
20
|
+
var url = new URL(href, location.href)
|
|
21
|
+
if (url.origin !== location.origin || url.pathname !== arg.path) return original(input, init)
|
|
22
|
+
window.__consentTraceCalls += 1
|
|
23
|
+
var answer = function () {
|
|
24
|
+
if (arg.fail === 'hang') return new Promise(function () {})
|
|
25
|
+
if (arg.fail === true) return Promise.resolve(new Response('not found', { status: 404 }))
|
|
26
|
+
return Promise.resolve(new Response(
|
|
27
|
+
'fl=0f0\\nh=' + location.hostname + '\\nip=203.0.113.7\\nts=0\\nvisit_scheme=https\\ncolo=TST\\nloc=' + arg.country + '\\n',
|
|
28
|
+
{ status: 200, headers: { 'content-type': 'text/plain' } }
|
|
29
|
+
))
|
|
30
|
+
}
|
|
31
|
+
return arg.delayMs > 0
|
|
32
|
+
? new Promise(function (resolve) { setTimeout(resolve, arg.delayMs) }).then(answer)
|
|
33
|
+
: answer()
|
|
34
|
+
}
|
|
35
|
+
})(${JSON.stringify(options)})` });
|
|
36
|
+
};
|
|
37
|
+
const traceCalls = async (page) => await page.evaluate('window.__consentTraceCalls || 0');
|
|
38
|
+
return { mockConsentGeo, traceCalls };
|
|
39
|
+
};
|
|
40
|
+
export const consentGeoTestHelper = createConsentGeoTestHelper();
|
|
41
|
+
//# sourceMappingURL=consent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent.js","sourceRoot":"","sources":["../src/consent.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,2BAA2B,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAA;AAG3F,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAyB,EAAE;IACnE,MAAM,cAAc,GAAG,KAAK,EAAE,MAA6B,EAAE,GAAmB,EAAiB,EAAE;QACjG,MAAM,OAAO,GAA+E;YAC1F,OAAO,EAAE,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,IAAI,2BAA2B;YACnF,OAAO,EAAE,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,IAAI,CAAC;YACvD,IAAI,EAAE,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,0BAA0B;YACnG,GAAG,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1E,GAAG,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxE,CAAA;QAED,8FAA8F;QAC9F,MAAM,MAAM,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;SAuBjC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IACpC,CAAC,CAAA;IAED,MAAM,UAAU,GAAG,KAAK,EAAE,IAAU,EAAmB,EAAE,CACvD,MAAM,IAAI,CAAC,QAAQ,CAAC,iCAAiC,CAAW,CAAA;IAElE,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,CAAA;AACvC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAG,0BAA0B,EAAE,CAAA"}
|
package/build/consts.local.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
export declare const DEFAULT_MARKETING_CONSENT_TIMEOUT = 30000;
|
|
2
2
|
export declare const DEFAULT_SUPERVISOR_PATH = "/authentication/login/pk-supervisor";
|
|
3
|
+
/** A consent country: a mocked visitor is asked unless the spec says otherwise. */
|
|
4
|
+
export declare const DEFAULT_CONSENT_GEO_COUNTRY = "PL";
|
|
5
|
+
/** `@owlmeans/consent`'s `CONSENT_TRACE_PATH`, repeated: this package depends on no consent package. */
|
|
6
|
+
export declare const DEFAULT_CONSENT_TRACE_PATH = "/cdn-cgi/trace";
|
|
7
|
+
/** What `acceptConsent` treats as "nothing to answer": the store settled on a decision without UI. */
|
|
8
|
+
export declare const CONSENT_SETTLED_WITHOUT_UI = "html[data-consent=\"decided\"], html[data-consent=\"idle\"]";
|
|
3
9
|
//# sourceMappingURL=consts.local.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consts.local.d.ts","sourceRoot":"","sources":["../src/consts.local.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iCAAiC,QAAS,CAAA;AAEvD,eAAO,MAAM,uBAAuB,wCAAwC,CAAA"}
|
|
1
|
+
{"version":3,"file":"consts.local.d.ts","sourceRoot":"","sources":["../src/consts.local.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iCAAiC,QAAS,CAAA;AAEvD,eAAO,MAAM,uBAAuB,wCAAwC,CAAA;AAE5E,mFAAmF;AACnF,eAAO,MAAM,2BAA2B,OAAO,CAAA;AAE/C,wGAAwG;AACxG,eAAO,MAAM,0BAA0B,mBAAmB,CAAA;AAE1D,sGAAsG;AACtG,eAAO,MAAM,0BAA0B,gEAA4D,CAAA"}
|
package/build/consts.local.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
export const DEFAULT_MARKETING_CONSENT_TIMEOUT = 30_000;
|
|
2
2
|
export const DEFAULT_SUPERVISOR_PATH = '/authentication/login/pk-supervisor';
|
|
3
|
+
/** A consent country: a mocked visitor is asked unless the spec says otherwise. */
|
|
4
|
+
export const DEFAULT_CONSENT_GEO_COUNTRY = 'PL';
|
|
5
|
+
/** `@owlmeans/consent`'s `CONSENT_TRACE_PATH`, repeated: this package depends on no consent package. */
|
|
6
|
+
export const DEFAULT_CONSENT_TRACE_PATH = '/cdn-cgi/trace';
|
|
7
|
+
/** What `acceptConsent` treats as "nothing to answer": the store settled on a decision without UI. */
|
|
8
|
+
export const CONSENT_SETTLED_WITHOUT_UI = 'html[data-consent="decided"], html[data-consent="idle"]';
|
|
3
9
|
//# sourceMappingURL=consts.local.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consts.local.js","sourceRoot":"","sources":["../src/consts.local.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,iCAAiC,GAAG,MAAM,CAAA;AAEvD,MAAM,CAAC,MAAM,uBAAuB,GAAG,qCAAqC,CAAA"}
|
|
1
|
+
{"version":3,"file":"consts.local.js","sourceRoot":"","sources":["../src/consts.local.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,iCAAiC,GAAG,MAAM,CAAA;AAEvD,MAAM,CAAC,MAAM,uBAAuB,GAAG,qCAAqC,CAAA;AAE5E,mFAAmF;AACnF,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,CAAA;AAE/C,wGAAwG;AACxG,MAAM,CAAC,MAAM,0BAA0B,GAAG,gBAAgB,CAAA;AAE1D,sGAAsG;AACtG,MAAM,CAAC,MAAM,0BAA0B,GAAG,yDAAyD,CAAA"}
|
package/build/index.d.ts
CHANGED
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,mBAAmB,oBAAoB,CAAA;AACvC,cAAc,wBAAwB,CAAA;AACtC,mBAAmB,YAAY,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,mBAAmB,iBAAiB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,mBAAmB,uBAAuB,CAAA;AAC1C,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,mBAAmB,oBAAoB,CAAA;AACvC,mBAAmB,oBAAoB,CAAA;AACvC,cAAc,wBAAwB,CAAA;AACtC,mBAAmB,YAAY,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,mBAAmB,iBAAiB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,mBAAmB,uBAAuB,CAAA;AAC1C,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA"}
|
package/build/index.js
CHANGED
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAG5B,cAAc,wBAAwB,CAAA;AAEtC,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AAEzB,cAAc,iBAAiB,CAAA"}
|
package/build/mount.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../src/mount.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../src/mount.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAYvD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,SAAgB,YAAY,KAAG,OAAO,CAAC,OAAO,CAexE,CAAA"}
|
package/build/mount.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { browserHelper } from './browser.js';
|
|
2
|
+
import { consentGeoTestHelper } from './consent.js';
|
|
2
3
|
const buildUrl = (opts) => {
|
|
3
4
|
const params = new URLSearchParams();
|
|
4
5
|
if (opts.component != null)
|
|
@@ -26,6 +27,9 @@ const buildUrl = (opts) => {
|
|
|
26
27
|
export const mountComponent = async (opts) => {
|
|
27
28
|
const browser = await browserHelper.launchBrowser();
|
|
28
29
|
const context = await browser.newContext();
|
|
30
|
+
if (opts.consentGeo != null) {
|
|
31
|
+
await consentGeoTestHelper.mockConsentGeo(context, opts.consentGeo);
|
|
32
|
+
}
|
|
29
33
|
const page = await context.newPage();
|
|
30
34
|
await page.goto(buildUrl(opts), {
|
|
31
35
|
waitUntil: opts.waitUntil ?? 'domcontentloaded',
|
package/build/mount.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount.js","sourceRoot":"","sources":["../src/mount.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"mount.js","sourceRoot":"","sources":["../src/mount.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAGnD,MAAM,QAAQ,GAAG,CAAC,IAAkB,EAAU,EAAE;IAC9C,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAA;IACpC,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI;QAAE,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAA;IACnE,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI;QAAE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;IACvE,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAA;IAC5B,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC,GAAG,CAAA;IAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA;IAC9C,OAAO,GAAG,IAAI,CAAC,GAAG,GAAG,GAAG,GAAG,EAAE,EAAE,CAAA;AACjC,CAAC,CAAA;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EAAE,IAAkB,EAAoB,EAAE;IAC3E,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,aAAa,EAAE,CAAA;IACnD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,CAAA;IAC1C,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;QAC5B,MAAM,oBAAoB,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;IACrE,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAA;IACpC,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;QAC9B,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,kBAAkB;QAC/C,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC3D,CAAC,CAAA;IACF,OAAO;QACL,IAAI;QACJ,KAAK,EAAE,KAAK,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,CAAA,CAAC,CAAC;KAC7C,CAAA;AACH,CAAC,CAAA"}
|
package/build/page/types.d.ts
CHANGED
|
@@ -120,6 +120,13 @@ export interface PageHelper {
|
|
|
120
120
|
* what the dialog is gating — but it is best-effort: an app without the widget, or one where the
|
|
121
121
|
* visitor already decided, simply has no dialog and this returns at once.
|
|
122
122
|
*
|
|
123
|
+
* It waits for the consent store to SETTLE (`<html data-consent>`), so a visitor still being
|
|
124
|
+
* located is waited out — the transparent overlay is up then and would take the click — and a
|
|
125
|
+
* visitor decided for automatically (outside the consent countries) returns `false` the moment
|
|
126
|
+
* the decision lands, rather than after the whole `timeout`. Whichever surface asks — the bottom
|
|
127
|
+
* bar or the preferences window — carries `[data-consent-dialog]` and `[data-consent-accept-all]`.
|
|
128
|
+
* Where the runner's own country would decide the outcome, pin it with `mockConsentGeo` first.
|
|
129
|
+
*
|
|
123
130
|
* @returns whether a dialog was actually answered.
|
|
124
131
|
*/
|
|
125
132
|
acceptConsent: (opts?: AcceptConsentOptions) => Promise<boolean>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/page/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,6BAA6B;IAC5C;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAAA;IAChC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,uDAAuD;AACvD,MAAM,WAAW,oBAAoB;IACnC,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,kHAAkH;IAClH,SAAS,CAAC,EAAE,QAAQ,GAAG,kBAAkB,GAAG,MAAM,GAAG,aAAa,CAAA;IAClE;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAA;CACrC;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAA;IACf,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,EAAE,EAAE,MAAM,CAAA;IACV,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,kGAAkG;IAClG,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,QAAQ,GAAG,kBAAkB,GAAG,MAAM,GAAG,aAAa,CAAA;IAClE;;;;OAIG;IACH,OAAO,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC7B,2FAA2F;IAC3F,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAA;CACrC;AAED,0FAA0F;AAC1F,MAAM,WAAW,UAAU;IACzB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,sBAAsB,EAAE,CAAC,IAAI,CAAC,EAAE,6BAA6B,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAClF;;;;;OAKG;IACH,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,sBAAsB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACpG;;;;;OAKG;IACH,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC9D
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/page/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,6BAA6B;IAC5C;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAAA;IAChC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,uDAAuD;AACvD,MAAM,WAAW,oBAAoB;IACnC,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,kHAAkH;IAClH,SAAS,CAAC,EAAE,QAAQ,GAAG,kBAAkB,GAAG,MAAM,GAAG,aAAa,CAAA;IAClE;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAA;CACrC;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAA;IACf,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,EAAE,EAAE,MAAM,CAAA;IACV,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,kGAAkG;IAClG,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,QAAQ,GAAG,kBAAkB,GAAG,MAAM,GAAG,aAAa,CAAA;IAClE;;;;OAIG;IACH,OAAO,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC7B,2FAA2F;IAC3F,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAA;CACrC;AAED,0FAA0F;AAC1F,MAAM,WAAW,UAAU;IACzB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,sBAAsB,EAAE,CAAC,IAAI,CAAC,EAAE,6BAA6B,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAClF;;;;;OAKG;IACH,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,sBAAsB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACpG;;;;;OAKG;IACH,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC9D;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,aAAa,EAAE,CAAC,IAAI,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAChE;;;;;OAKG;IACH,sBAAsB,EAAE,CAAC,IAAI,EAAE,0BAA0B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5E"}
|
package/build/page.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../src/page.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../src/page.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAItC,OAAO,KAAK,EACmE,UAAU,EACxF,MAAM,iBAAiB,CAAA;AAExB,eAAO,MAAM,cAAc,SAAU,IAAI,KAAG,UAuL3C,CAAA"}
|
package/build/page.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir } from 'node:fs/promises';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
|
-
import { DEFAULT_MARKETING_CONSENT_TIMEOUT, DEFAULT_SUPERVISOR_PATH } from './consts.local.js';
|
|
3
|
+
import { CONSENT_SETTLED_WITHOUT_UI, DEFAULT_MARKETING_CONSENT_TIMEOUT, DEFAULT_SUPERVISOR_PATH, } from './consts.local.js';
|
|
4
4
|
export const makePageHelper = (page) => {
|
|
5
5
|
const answerMarketingConsent = async (opts) => {
|
|
6
6
|
const timeout = opts?.timeout ?? DEFAULT_MARKETING_CONSENT_TIMEOUT;
|
|
@@ -107,12 +107,20 @@ export const makePageHelper = (page) => {
|
|
|
107
107
|
};
|
|
108
108
|
const acceptConsent = async (opts) => {
|
|
109
109
|
const dialog = page.locator('[data-consent-dialog]');
|
|
110
|
+
// Wait until the consent store has SETTLED: a surface is up, or a decision exists without one
|
|
111
|
+
// (stored earlier, or made automatically outside the consent countries — `<html data-consent>`).
|
|
112
|
+
// Never while the visitor is still being located: the overlay is up then and a click would land
|
|
113
|
+
// on it. An application older than the attribute only ever settles by showing the dialog.
|
|
110
114
|
try {
|
|
111
|
-
await
|
|
115
|
+
await page.locator(`[data-consent-dialog], ${CONSENT_SETTLED_WITHOUT_UI}`)
|
|
116
|
+
.first().waitFor({ state: 'visible', timeout: opts?.timeout ?? 5_000 });
|
|
112
117
|
}
|
|
113
118
|
catch {
|
|
114
119
|
return false;
|
|
115
120
|
}
|
|
121
|
+
if (!await dialog.first().isVisible()) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
116
124
|
// Accept-all rather than essential-only: a test asserting a narrower decision should make that
|
|
117
125
|
// decision itself, and silently choosing the minimum here would hide it from the spec.
|
|
118
126
|
await page.locator('[data-consent-accept-all]').click();
|
package/build/page.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.js","sourceRoot":"","sources":["../src/page.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AACxC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEzC,OAAO,EAAE,iCAAiC,EAAE,uBAAuB,
|
|
1
|
+
{"version":3,"file":"page.js","sourceRoot":"","sources":["../src/page.ts"],"names":[],"mappings":"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"}
|
package/build/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Page } from 'playwright';
|
|
2
|
+
import type { ConsentGeoMock } from './consent/types.js';
|
|
2
3
|
export interface MountOptions {
|
|
3
4
|
/**
|
|
4
5
|
* URL the harness is reachable at. The consuming package is in charge
|
|
@@ -31,6 +32,11 @@ export interface MountOptions {
|
|
|
31
32
|
waitUntil?: 'commit' | 'domcontentloaded' | 'load' | 'networkidle';
|
|
32
33
|
/** Navigation timeout in ms. */
|
|
33
34
|
timeout?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Answer the page's Cloudflare trace with this — installed BEFORE the first navigation, so the
|
|
37
|
+
* consent geo gate sees it on the very first load. See `consentGeoTestHelper.mockConsentGeo`.
|
|
38
|
+
*/
|
|
39
|
+
consentGeo?: ConsentGeoMock;
|
|
34
40
|
}
|
|
35
41
|
export interface Mounted {
|
|
36
42
|
page: Page;
|
package/build/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAExD,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,GAAG,EAAE,MAAM,CAAA;IACX;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,QAAQ,GAAG,kBAAkB,GAAG,MAAM,GAAG,aAAa,CAAA;IAClE,gCAAgC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,UAAU,CAAC,EAAE,cAAc,CAAA;CAC5B;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,IAAI,CAAA;IACV,0EAA0E;IAC1E,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/test-ui",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.52",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
"./harness": "./harness/index.html"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@owlmeans/auth": "^0.1.18-rc.
|
|
26
|
-
"@owlmeans/basic-envelope": "^0.1.18-rc.
|
|
27
|
-
"@owlmeans/basic-ids": "^0.1.18-rc.
|
|
28
|
-
"@owlmeans/basic-keys": "^0.1.18-rc.
|
|
29
|
-
"@owlmeans/test": "^0.1.18-rc.
|
|
30
|
-
"@owlmeans/test-auth": "^0.1.18-rc.
|
|
25
|
+
"@owlmeans/auth": "^0.1.18-rc.41",
|
|
26
|
+
"@owlmeans/basic-envelope": "^0.1.18-rc.49",
|
|
27
|
+
"@owlmeans/basic-ids": "^0.1.18-rc.40",
|
|
28
|
+
"@owlmeans/basic-keys": "^0.1.18-rc.51",
|
|
29
|
+
"@owlmeans/test": "^0.1.18-rc.36",
|
|
30
|
+
"@owlmeans/test-auth": "^0.1.18-rc.49",
|
|
31
31
|
"playwright": "^1.63.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { BrowserContext, Page } from 'playwright'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A fake answer from Cloudflare's trace endpoint: a country code (`'US'`), or
|
|
5
|
+
* `{ country?, fail?, delayMs?, gpc? }`.
|
|
6
|
+
*/
|
|
7
|
+
export type ConsentGeoMock = string | ConsentGeoMockOptions
|
|
8
|
+
|
|
9
|
+
export interface ConsentGeoMockOptions {
|
|
10
|
+
/** The `loc=` the fake trace answers with. Defaults to `PL` — a consent country. */
|
|
11
|
+
country?: string
|
|
12
|
+
/** `true` answers 404 (the host is not behind Cloudflare); `'hang'` never answers. */
|
|
13
|
+
fail?: boolean | 'hang'
|
|
14
|
+
/** Answer only after this many ms — long enough to see the spinner. */
|
|
15
|
+
delayMs?: number
|
|
16
|
+
/** Send Global Privacy Control (`navigator.globalPrivacyControl`). */
|
|
17
|
+
gpc?: boolean
|
|
18
|
+
/** The trace path the page asks. Defaults to `/cdn-cgi/trace`. */
|
|
19
|
+
path?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Pinning where a visitor is, so a consent test does not depend on where its runner happens to be:
|
|
24
|
+
* every app behind Cloudflare answers the trace with the RUNNER's country.
|
|
25
|
+
*/
|
|
26
|
+
export interface ConsentGeoTestHelper {
|
|
27
|
+
/**
|
|
28
|
+
* Answer the page's same-origin trace request from inside the page: an init script that wraps
|
|
29
|
+
* `window.fetch` before any page script runs, and counts the calls in
|
|
30
|
+
* `window.__consentTraceCalls`. Never `page.route` — interception disables the cache for every
|
|
31
|
+
* request, and a vite-served application stalls on its hundreds of unbundled modules.
|
|
32
|
+
*
|
|
33
|
+
* Applies to the NEXT navigation of the page or context it is installed on.
|
|
34
|
+
*/
|
|
35
|
+
mockConsentGeo: (target: Page | BrowserContext, geo: ConsentGeoMock) => Promise<void>
|
|
36
|
+
/** How many trace requests the page made since it loaded (`mockConsentGeo` only). */
|
|
37
|
+
traceCalls: (page: Page) => Promise<number>
|
|
38
|
+
}
|
package/src/consent.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { BrowserContext, Page } from 'playwright'
|
|
2
|
+
import { DEFAULT_CONSENT_GEO_COUNTRY, DEFAULT_CONSENT_TRACE_PATH } from './consts.local.js'
|
|
3
|
+
import type { ConsentGeoMock, ConsentGeoMockOptions, ConsentGeoTestHelper } from './consent/types.js'
|
|
4
|
+
|
|
5
|
+
export const createConsentGeoTestHelper = (): ConsentGeoTestHelper => {
|
|
6
|
+
const mockConsentGeo = async (target: Page | BrowserContext, geo: ConsentGeoMock): Promise<void> => {
|
|
7
|
+
const options: ConsentGeoMockOptions & { country: string, delayMs: number, path: string } = {
|
|
8
|
+
country: typeof geo === 'string' ? geo : geo.country ?? DEFAULT_CONSENT_GEO_COUNTRY,
|
|
9
|
+
delayMs: typeof geo === 'string' ? 0 : geo.delayMs ?? 0,
|
|
10
|
+
path: typeof geo === 'string' ? DEFAULT_CONSENT_TRACE_PATH : geo.path ?? DEFAULT_CONSENT_TRACE_PATH,
|
|
11
|
+
...(typeof geo !== 'string' && geo.fail != null ? { fail: geo.fail } : {}),
|
|
12
|
+
...(typeof geo !== 'string' && geo.gpc != null ? { gpc: geo.gpc } : {}),
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// A string, not a function: it runs in the page, and this package compiles without DOM types.
|
|
16
|
+
await target.addInitScript({ content: `(function(arg){
|
|
17
|
+
if (arg.gpc != null) {
|
|
18
|
+
Object.defineProperty(Navigator.prototype, 'globalPrivacyControl', { get: function () { return arg.gpc }, configurable: true })
|
|
19
|
+
}
|
|
20
|
+
window.__consentTraceCalls = 0
|
|
21
|
+
var original = window.fetch.bind(window)
|
|
22
|
+
window.fetch = function (input, init) {
|
|
23
|
+
var href = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url
|
|
24
|
+
var url = new URL(href, location.href)
|
|
25
|
+
if (url.origin !== location.origin || url.pathname !== arg.path) return original(input, init)
|
|
26
|
+
window.__consentTraceCalls += 1
|
|
27
|
+
var answer = function () {
|
|
28
|
+
if (arg.fail === 'hang') return new Promise(function () {})
|
|
29
|
+
if (arg.fail === true) return Promise.resolve(new Response('not found', { status: 404 }))
|
|
30
|
+
return Promise.resolve(new Response(
|
|
31
|
+
'fl=0f0\\nh=' + location.hostname + '\\nip=203.0.113.7\\nts=0\\nvisit_scheme=https\\ncolo=TST\\nloc=' + arg.country + '\\n',
|
|
32
|
+
{ status: 200, headers: { 'content-type': 'text/plain' } }
|
|
33
|
+
))
|
|
34
|
+
}
|
|
35
|
+
return arg.delayMs > 0
|
|
36
|
+
? new Promise(function (resolve) { setTimeout(resolve, arg.delayMs) }).then(answer)
|
|
37
|
+
: answer()
|
|
38
|
+
}
|
|
39
|
+
})(${JSON.stringify(options)})` })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const traceCalls = async (page: Page): Promise<number> =>
|
|
43
|
+
await page.evaluate('window.__consentTraceCalls || 0') as number
|
|
44
|
+
|
|
45
|
+
return { mockConsentGeo, traceCalls }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const consentGeoTestHelper = createConsentGeoTestHelper()
|
package/src/consts.local.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
export const DEFAULT_MARKETING_CONSENT_TIMEOUT = 30_000
|
|
2
2
|
|
|
3
3
|
export const DEFAULT_SUPERVISOR_PATH = '/authentication/login/pk-supervisor'
|
|
4
|
+
|
|
5
|
+
/** A consent country: a mocked visitor is asked unless the spec says otherwise. */
|
|
6
|
+
export const DEFAULT_CONSENT_GEO_COUNTRY = 'PL'
|
|
7
|
+
|
|
8
|
+
/** `@owlmeans/consent`'s `CONSENT_TRACE_PATH`, repeated: this package depends on no consent package. */
|
|
9
|
+
export const DEFAULT_CONSENT_TRACE_PATH = '/cdn-cgi/trace'
|
|
10
|
+
|
|
11
|
+
/** What `acceptConsent` treats as "nothing to answer": the store settled on a decision without UI. */
|
|
12
|
+
export const CONSENT_SETTLED_WITHOUT_UI = 'html[data-consent="decided"], html[data-consent="idle"]'
|
package/src/index.ts
CHANGED
package/src/mount.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { browserHelper } from './browser.js'
|
|
2
|
+
import { consentGeoTestHelper } from './consent.js'
|
|
2
3
|
import type { Mounted, MountOptions } from './types.js'
|
|
3
4
|
|
|
4
5
|
const buildUrl = (opts: MountOptions): string => {
|
|
@@ -26,6 +27,9 @@ const buildUrl = (opts: MountOptions): string => {
|
|
|
26
27
|
export const mountComponent = async (opts: MountOptions): Promise<Mounted> => {
|
|
27
28
|
const browser = await browserHelper.launchBrowser()
|
|
28
29
|
const context = await browser.newContext()
|
|
30
|
+
if (opts.consentGeo != null) {
|
|
31
|
+
await consentGeoTestHelper.mockConsentGeo(context, opts.consentGeo)
|
|
32
|
+
}
|
|
29
33
|
const page = await context.newPage()
|
|
30
34
|
await page.goto(buildUrl(opts), {
|
|
31
35
|
waitUntil: opts.waitUntil ?? 'domcontentloaded',
|
package/src/page/types.ts
CHANGED
|
@@ -124,6 +124,13 @@ export interface PageHelper {
|
|
|
124
124
|
* what the dialog is gating — but it is best-effort: an app without the widget, or one where the
|
|
125
125
|
* visitor already decided, simply has no dialog and this returns at once.
|
|
126
126
|
*
|
|
127
|
+
* It waits for the consent store to SETTLE (`<html data-consent>`), so a visitor still being
|
|
128
|
+
* located is waited out — the transparent overlay is up then and would take the click — and a
|
|
129
|
+
* visitor decided for automatically (outside the consent countries) returns `false` the moment
|
|
130
|
+
* the decision lands, rather than after the whole `timeout`. Whichever surface asks — the bottom
|
|
131
|
+
* bar or the preferences window — carries `[data-consent-dialog]` and `[data-consent-accept-all]`.
|
|
132
|
+
* Where the runner's own country would decide the outcome, pin it with `mockConsentGeo` first.
|
|
133
|
+
*
|
|
127
134
|
* @returns whether a dialog was actually answered.
|
|
128
135
|
*/
|
|
129
136
|
acceptConsent: (opts?: AcceptConsentOptions) => Promise<boolean>
|
package/src/page.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { mkdir } from 'node:fs/promises'
|
|
2
2
|
import { join, resolve } from 'node:path'
|
|
3
3
|
import type { Page } from 'playwright'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
CONSENT_SETTLED_WITHOUT_UI, DEFAULT_MARKETING_CONSENT_TIMEOUT, DEFAULT_SUPERVISOR_PATH,
|
|
6
|
+
} from './consts.local.js'
|
|
5
7
|
import type {
|
|
6
8
|
AcceptConsentOptions, AnswerMarketingConsentOptions, DispatcherLoginOptions, PageHelper, SupervisorFormLoginOptions,
|
|
7
9
|
} from './page/types.js'
|
|
@@ -126,11 +128,19 @@ export const makePageHelper = (page: Page): PageHelper => {
|
|
|
126
128
|
|
|
127
129
|
const acceptConsent = async (opts?: AcceptConsentOptions): Promise<boolean> => {
|
|
128
130
|
const dialog = page.locator('[data-consent-dialog]')
|
|
131
|
+
// Wait until the consent store has SETTLED: a surface is up, or a decision exists without one
|
|
132
|
+
// (stored earlier, or made automatically outside the consent countries — `<html data-consent>`).
|
|
133
|
+
// Never while the visitor is still being located: the overlay is up then and a click would land
|
|
134
|
+
// on it. An application older than the attribute only ever settles by showing the dialog.
|
|
129
135
|
try {
|
|
130
|
-
await
|
|
136
|
+
await page.locator(`[data-consent-dialog], ${CONSENT_SETTLED_WITHOUT_UI}`)
|
|
137
|
+
.first().waitFor({ state: 'visible', timeout: opts?.timeout ?? 5_000 })
|
|
131
138
|
} catch {
|
|
132
139
|
return false
|
|
133
140
|
}
|
|
141
|
+
if (!await dialog.first().isVisible()) {
|
|
142
|
+
return false
|
|
143
|
+
}
|
|
134
144
|
// Accept-all rather than essential-only: a test asserting a narrower decision should make that
|
|
135
145
|
// decision itself, and silently choosing the minimum here would hide it from the spec.
|
|
136
146
|
await page.locator('[data-consent-accept-all]').click()
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Page } from 'playwright'
|
|
2
|
+
import type { ConsentGeoMock } from './consent/types.js'
|
|
2
3
|
|
|
3
4
|
export interface MountOptions {
|
|
4
5
|
/**
|
|
@@ -32,6 +33,11 @@ export interface MountOptions {
|
|
|
32
33
|
waitUntil?: 'commit' | 'domcontentloaded' | 'load' | 'networkidle'
|
|
33
34
|
/** Navigation timeout in ms. */
|
|
34
35
|
timeout?: number
|
|
36
|
+
/**
|
|
37
|
+
* Answer the page's Cloudflare trace with this — installed BEFORE the first navigation, so the
|
|
38
|
+
* consent geo gate sees it on the very first load. See `consentGeoTestHelper.mockConsentGeo`.
|
|
39
|
+
*/
|
|
40
|
+
consentGeo?: ConsentGeoMock
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
export interface Mounted {
|