@aerogel/playwright 0.1.2
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/dist/aerogel-playwright.d.ts +58 -0
- package/dist/aerogel-playwright.js +117 -0
- package/dist/aerogel-playwright.js.map +1 -0
- package/package.json +49 -0
- package/src/index.ts +3 -0
- package/src/lib/a11y.ts +93 -0
- package/src/lib/errors.ts +7 -0
- package/src/lib/index.ts +5 -0
- package/src/lib/models.ts +31 -0
- package/src/lib/snapshots.ts +14 -0
- package/src/lib/sync.ts +9 -0
- package/src/setup/errors.ts +27 -0
- package/src/setup/index.ts +22 -0
- package/src/setup/storage.ts +12 -0
- package/src/solid/index.ts +1 -0
- package/src/types/global.d.ts +7 -0
- package/src/types/solid.d.ts +1 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { expect } from '@playwright/test';
|
|
2
|
+
import { GetModelInput } from 'soukai-bis';
|
|
3
|
+
import { Locator } from '@playwright/test';
|
|
4
|
+
import { ModelsRegistry } from 'soukai-bis';
|
|
5
|
+
import { Page } from '@playwright/test';
|
|
6
|
+
import { PlaywrightTestArgs } from '@playwright/test';
|
|
7
|
+
import { PlaywrightTestOptions } from '@playwright/test';
|
|
8
|
+
import { PlaywrightWorkerArgs } from '@playwright/test';
|
|
9
|
+
import { PlaywrightWorkerOptions } from '@playwright/test';
|
|
10
|
+
import { TestType } from '@playwright/test';
|
|
11
|
+
|
|
12
|
+
export declare function a11yGet(page: Page, text: string): Locator;
|
|
13
|
+
|
|
14
|
+
export declare function ariaInput(page: Page, label: string, options?: AriaInputOptions): Locator;
|
|
15
|
+
|
|
16
|
+
export declare type AriaInputOptions = {
|
|
17
|
+
description?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export declare function ariaLabel(page: Page, label: string): Locator;
|
|
21
|
+
|
|
22
|
+
export declare function comboboxSelect(page: Page, labelOrSelector: string, value: string): Promise<void>;
|
|
23
|
+
|
|
24
|
+
export declare function count<T extends keyof ModelsRegistry>(page: Page, name: T): Promise<number>;
|
|
25
|
+
|
|
26
|
+
export declare function create<T extends keyof ModelsRegistry>(page: Page, name: T, data: GetModelInput<ModelsRegistry[T]>): Promise<void>;
|
|
27
|
+
|
|
28
|
+
export declare function disableErrorHandling(page: Page): Promise<void>;
|
|
29
|
+
|
|
30
|
+
export declare function dontSee(page: Page, text: string, options?: {
|
|
31
|
+
timeout?: number;
|
|
32
|
+
}): Promise<void>;
|
|
33
|
+
|
|
34
|
+
export { expect }
|
|
35
|
+
|
|
36
|
+
export declare function matchImageSnapshot(page: Page, name?: string): Promise<void>;
|
|
37
|
+
|
|
38
|
+
export declare function press(page: Page, label: string, selector?: string): Promise<void>;
|
|
39
|
+
|
|
40
|
+
export declare function see(page: Page, text: string, selectorOrOptions?: string | SeeOptions, options?: SeeOptions): Promise<Locator>;
|
|
41
|
+
|
|
42
|
+
export declare function seeImage(page: Page, alt: string): Promise<void>;
|
|
43
|
+
|
|
44
|
+
export declare type SeeOptions = {
|
|
45
|
+
srOnly?: boolean;
|
|
46
|
+
timeout?: number;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export declare const test: TestType<PlaywrightTestArgs & PlaywrightTestOptions & {
|
|
50
|
+
resetStorage: void;
|
|
51
|
+
}, PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
|
|
52
|
+
|
|
53
|
+
export declare function waitSync(page: Page): Promise<void>;
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
export * from "playwright-solid";
|
|
57
|
+
|
|
58
|
+
export { }
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { expect as r, test as u } from "@playwright/test";
|
|
2
|
+
import { expect as B } from "@playwright/test";
|
|
3
|
+
export * from "playwright-solid";
|
|
4
|
+
function l(t, i) {
|
|
5
|
+
return t.getByText(i, { exact: !1 }).or(t.locator(`[aria-label="${i}"]`)).first();
|
|
6
|
+
}
|
|
7
|
+
function y(t, i) {
|
|
8
|
+
return t.locator(`[aria-label="${i}"]`);
|
|
9
|
+
}
|
|
10
|
+
function g(t, i, a = {}) {
|
|
11
|
+
let e = t.getByLabel(i, { exact: !0 });
|
|
12
|
+
return a.description && (e = e.filter({
|
|
13
|
+
has: t.locator(`[aria-description="${a.description}"], [aria-describedby]`)
|
|
14
|
+
})), e;
|
|
15
|
+
}
|
|
16
|
+
async function w(t, i, a) {
|
|
17
|
+
await r(l(t, i)).not.toBeVisible({ timeout: a == null ? void 0 : a.timeout });
|
|
18
|
+
}
|
|
19
|
+
async function h(t, i, a) {
|
|
20
|
+
const e = a ?? 'button:visible, a:visible, label:visible, details:visible, [role="menuitem"]:visible', s = t.locator('[role="dialog"][data-state="open"]:not([aria-hidden="true"])').last().locator(e).filter({ hasText: i });
|
|
21
|
+
await (await s.count() > 0 ? s.first() : t.locator(e).filter({ hasText: i }).first()).click();
|
|
22
|
+
}
|
|
23
|
+
async function f(t, i, a = {}, e = {}) {
|
|
24
|
+
const o = typeof a == "string" ? e.srOnly : a.srOnly, c = (typeof a == "string" ? e : a).timeout;
|
|
25
|
+
let n;
|
|
26
|
+
return typeof a == "string" ? n = t.locator(a).filter({ hasText: i }).first() : n = l(t, i), o ? (await r(n).toBeAttached({ timeout: c }), n) : (await r(n).toBeVisible({ timeout: c }), n);
|
|
27
|
+
}
|
|
28
|
+
async function S(t, i) {
|
|
29
|
+
const a = t.locator(`img[alt="${i}"]`).first();
|
|
30
|
+
await a.scrollIntoViewIfNeeded(), await r(a).toBeVisible();
|
|
31
|
+
}
|
|
32
|
+
async function T(t, i, a) {
|
|
33
|
+
await t.waitForTimeout(300);
|
|
34
|
+
const o = await t.locator("label[for]").filter({ hasText: i }).first().getAttribute("for");
|
|
35
|
+
o ? await t.locator(`#${o}`).click() : await t.locator(i).click(), await t.waitForTimeout(300), await t.locator('*[role="option"]').filter({ hasText: a }).first().click();
|
|
36
|
+
}
|
|
37
|
+
async function v(t) {
|
|
38
|
+
await t.addInitScript(() => {
|
|
39
|
+
window.__aerogelDisableErrorHandling__ = !0;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async function x(t, i, a) {
|
|
43
|
+
await t.evaluate(
|
|
44
|
+
async ({ modelName: e, modelData: o }) => {
|
|
45
|
+
if (!window.testingRuntime)
|
|
46
|
+
throw new Error("Testing runtime is not available");
|
|
47
|
+
await window.testingRuntime.model(e).create(o);
|
|
48
|
+
},
|
|
49
|
+
{ modelName: i, modelData: a }
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
async function _(t, i) {
|
|
53
|
+
return t.evaluate(async (a) => {
|
|
54
|
+
if (!window.testingRuntime)
|
|
55
|
+
throw new Error("Testing runtime is not available");
|
|
56
|
+
return (await window.testingRuntime.model(a).all()).length;
|
|
57
|
+
}, i);
|
|
58
|
+
}
|
|
59
|
+
async function E(t, i) {
|
|
60
|
+
!process.env.SNAPSHOTS && !process.env.PLAYWRIGHT_SNAPSHOTS || (i ? await r(t).toHaveScreenshot(`${i}.png`, { animations: "disabled" }) : await r(t).toHaveScreenshot({ animations: "disabled" }));
|
|
61
|
+
}
|
|
62
|
+
async function H(t) {
|
|
63
|
+
await f(t, "Synchronization in progress"), await w(t, "Synchronization in progress", { timeout: 1e4 }), await t.waitForTimeout(100);
|
|
64
|
+
}
|
|
65
|
+
async function d(t, i) {
|
|
66
|
+
const a = t.url(), e = a !== "about:blank" ? new URL(a).origin : i ?? "http://localhost:5001";
|
|
67
|
+
await (await t.context().newCDPSession(t)).send("Storage.clearDataForOrigin", {
|
|
68
|
+
origin: e,
|
|
69
|
+
storageTypes: "all"
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async function m(t) {
|
|
73
|
+
await t.addInitScript(() => {
|
|
74
|
+
window.__aerogelEvents__ = {
|
|
75
|
+
error: ({ error: i, message: a }) => {
|
|
76
|
+
if (window.__aerogelDisableErrorHandling__)
|
|
77
|
+
return;
|
|
78
|
+
const e = i instanceof Error ? i : new Error(a || String(i));
|
|
79
|
+
setTimeout(() => {
|
|
80
|
+
throw e;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}), t.on("pageerror", (i) => {
|
|
85
|
+
if (!i.message.includes("ResizeObserver loop completed with undelivered notifications."))
|
|
86
|
+
throw i;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
const I = u.extend({
|
|
90
|
+
page: async ({ page: t }, i) => {
|
|
91
|
+
await m(t), await i(t);
|
|
92
|
+
},
|
|
93
|
+
resetStorage: [
|
|
94
|
+
async ({ page: t, baseURL: i }, a) => {
|
|
95
|
+
await d(t, i), await a();
|
|
96
|
+
},
|
|
97
|
+
{ auto: !0 }
|
|
98
|
+
]
|
|
99
|
+
});
|
|
100
|
+
export {
|
|
101
|
+
l as a11yGet,
|
|
102
|
+
g as ariaInput,
|
|
103
|
+
y as ariaLabel,
|
|
104
|
+
T as comboboxSelect,
|
|
105
|
+
_ as count,
|
|
106
|
+
x as create,
|
|
107
|
+
v as disableErrorHandling,
|
|
108
|
+
w as dontSee,
|
|
109
|
+
B as expect,
|
|
110
|
+
E as matchImageSnapshot,
|
|
111
|
+
h as press,
|
|
112
|
+
f as see,
|
|
113
|
+
S as seeImage,
|
|
114
|
+
I as test,
|
|
115
|
+
H as waitSync
|
|
116
|
+
};
|
|
117
|
+
//# sourceMappingURL=aerogel-playwright.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aerogel-playwright.js","sources":["../src/lib/a11y.ts","../src/lib/errors.ts","../src/lib/models.ts","../src/lib/snapshots.ts","../src/lib/sync.ts","../src/setup/storage.ts","../src/setup/errors.ts","../src/setup/index.ts"],"sourcesContent":["import { expect } from '@playwright/test';\nimport type { Locator, Page } from '@playwright/test';\n\nexport type SeeOptions = {\n srOnly?: boolean;\n timeout?: number;\n};\n\nexport type AriaInputOptions = {\n description?: string;\n};\n\nexport function a11yGet(page: Page, text: string): Locator {\n return page\n .getByText(text, { exact: false })\n .or(page.locator(`[aria-label=\"${text}\"]`))\n .first();\n}\n\nexport function ariaLabel(page: Page, label: string): Locator {\n return page.locator(`[aria-label=\"${label}\"]`);\n}\n\nexport function ariaInput(page: Page, label: string, options: AriaInputOptions = {}): Locator {\n let locator = page.getByLabel(label, { exact: true });\n if (options.description) {\n locator = locator.filter({\n has: page.locator(`[aria-description=\"${options.description}\"], [aria-describedby]`),\n });\n }\n return locator;\n}\n\nexport async function dontSee(page: Page, text: string, options?: { timeout?: number }): Promise<void> {\n await expect(a11yGet(page, text)).not.toBeVisible({ timeout: options?.timeout });\n}\n\nexport async function press(page: Page, label: string, selector?: string): Promise<void> {\n const targetSelector =\n selector ?? 'button:visible, a:visible, label:visible, details:visible, [role=\"menuitem\"]:visible';\n const dialog = page.locator('[role=\"dialog\"][data-state=\"open\"]:not([aria-hidden=\"true\"])').last();\n const inDialog = dialog.locator(targetSelector).filter({ hasText: label });\n const locator =\n (await inDialog.count()) > 0\n ? inDialog.first()\n : page.locator(targetSelector).filter({ hasText: label }).first();\n await locator.click();\n}\n\nexport async function see(\n page: Page,\n text: string,\n selectorOrOptions: string | SeeOptions = {},\n options: SeeOptions = {},\n): Promise<Locator> {\n const srOnly = typeof selectorOrOptions === 'string' ? options.srOnly : selectorOrOptions.srOnly;\n const baseOptions = typeof selectorOrOptions === 'string' ? options : selectorOrOptions;\n const timeout = baseOptions.timeout;\n\n let locator: Locator;\n if (typeof selectorOrOptions === 'string') {\n locator = page.locator(selectorOrOptions).filter({ hasText: text }).first();\n } else {\n locator = a11yGet(page, text);\n }\n\n if (srOnly) {\n await expect(locator).toBeAttached({ timeout });\n return locator;\n }\n\n await expect(locator).toBeVisible({ timeout });\n return locator;\n}\n\nexport async function seeImage(page: Page, alt: string): Promise<void> {\n const locator = page.locator(`img[alt=\"${alt}\"]`).first();\n await locator.scrollIntoViewIfNeeded();\n await expect(locator).toBeVisible();\n}\n\nexport async function comboboxSelect(page: Page, labelOrSelector: string, value: string): Promise<void> {\n await page.waitForTimeout(300);\n const label = page.locator('label[for]').filter({ hasText: labelOrSelector }).first();\n const id = await label.getAttribute('for');\n if (id) {\n await page.locator(`#${id}`).click();\n } else {\n await page.locator(labelOrSelector).click();\n }\n await page.waitForTimeout(300);\n await page.locator('*[role=\"option\"]').filter({ hasText: value }).first().click();\n}\n","import type { Page } from '@playwright/test';\n\nexport async function disableErrorHandling(page: Page): Promise<void> {\n await page.addInitScript(() => {\n window.__aerogelDisableErrorHandling__ = true;\n });\n}\n","import type { Page } from '@playwright/test';\nimport type { GetModelInput, ModelsRegistry } from 'soukai-bis';\n\nexport async function create<T extends keyof ModelsRegistry>(\n page: Page,\n name: T,\n data: GetModelInput<ModelsRegistry[T]>,\n): Promise<void> {\n await page.evaluate(\n async ({ modelName, modelData }) => {\n if (!window.testingRuntime) {\n throw new Error('Testing runtime is not available');\n }\n\n await window.testingRuntime.model(modelName as string).create(modelData);\n },\n { modelName: name, modelData: data },\n );\n}\n\nexport async function count<T extends keyof ModelsRegistry>(page: Page, name: T): Promise<number> {\n return page.evaluate(async (modelName) => {\n if (!window.testingRuntime) {\n throw new Error('Testing runtime is not available');\n }\n\n const instances = await window.testingRuntime.model(modelName as string).all();\n\n return instances.length;\n }, name);\n}\n","import { expect } from '@playwright/test';\nimport type { Page } from '@playwright/test';\n\nexport async function matchImageSnapshot(page: Page, name?: string): Promise<void> {\n if (!process.env.SNAPSHOTS && !process.env.PLAYWRIGHT_SNAPSHOTS) {\n return;\n }\n\n if (name) {\n await expect(page).toHaveScreenshot(`${name}.png`, { animations: 'disabled' });\n } else {\n await expect(page).toHaveScreenshot({ animations: 'disabled' });\n }\n}\n","import type { Page } from '@playwright/test';\n\nimport { dontSee, see } from './a11y';\n\nexport async function waitSync(page: Page): Promise<void> {\n await see(page, 'Synchronization in progress');\n await dontSee(page, 'Synchronization in progress', { timeout: 10000 });\n await page.waitForTimeout(100);\n}\n","import type { Page } from '@playwright/test';\n\nexport async function resetStorage(page: Page, baseURL?: string | null): Promise<void> {\n const url = page.url();\n const origin = url !== 'about:blank' ? new URL(url).origin : (baseURL ?? 'http://localhost:5001');\n const client = await page.context().newCDPSession(page);\n\n await client.send('Storage.clearDataForOrigin', {\n origin,\n storageTypes: 'all',\n });\n}\n","import type { Page } from '@playwright/test';\n\nexport async function setupErrorListener(page: Page): Promise<void> {\n await page.addInitScript(() => {\n window.__aerogelEvents__ = {\n error: ({ error, message }: { error: unknown; message?: string }) => {\n if (window.__aerogelDisableErrorHandling__) {\n return;\n }\n\n const errorToThrow = error instanceof Error ? error : new Error(message || String(error));\n\n setTimeout(() => {\n throw errorToThrow;\n });\n },\n };\n });\n\n page.on('pageerror', (exception) => {\n if (exception.message.includes('ResizeObserver loop completed with undelivered notifications.')) {\n return;\n }\n\n throw exception;\n });\n}\n","import { test as base, expect } from '@playwright/test';\n\nimport { resetStorage } from './storage';\nimport { setupErrorListener } from './errors';\n\nexport const test = base.extend<{\n resetStorage: void;\n}>({\n page: async ({ page }, use) => {\n await setupErrorListener(page);\n await use(page);\n },\n resetStorage: [\n async ({ page, baseURL }, use) => {\n await resetStorage(page, baseURL);\n await use();\n },\n { auto: true },\n ],\n});\n\nexport { expect };\n"],"names":["a11yGet","page","text","ariaLabel","label","ariaInput","options","locator","dontSee","expect","press","selector","targetSelector","inDialog","see","selectorOrOptions","srOnly","timeout","seeImage","alt","comboboxSelect","labelOrSelector","value","id","disableErrorHandling","create","name","data","modelName","modelData","count","matchImageSnapshot","waitSync","resetStorage","baseURL","url","origin","setupErrorListener","error","message","errorToThrow","exception","test","base","use"],"mappings":";;;AAYO,SAASA,EAAQC,GAAYC,GAAuB;AACvD,SAAOD,EACF,UAAUC,GAAM,EAAE,OAAO,IAAO,EAChC,GAAGD,EAAK,QAAQ,gBAAgBC,CAAI,IAAI,CAAC,EACzC,MAAA;AACT;AAEO,SAASC,EAAUF,GAAYG,GAAwB;AAC1D,SAAOH,EAAK,QAAQ,gBAAgBG,CAAK,IAAI;AACjD;AAEO,SAASC,EAAUJ,GAAYG,GAAeE,IAA4B,CAAA,GAAa;AAC1F,MAAIC,IAAUN,EAAK,WAAWG,GAAO,EAAE,OAAO,IAAM;AACpD,SAAIE,EAAQ,gBACRC,IAAUA,EAAQ,OAAO;AAAA,IACrB,KAAKN,EAAK,QAAQ,sBAAsBK,EAAQ,WAAW,wBAAwB;AAAA,EAAA,CACtF,IAEEC;AACX;AAEA,eAAsBC,EAAQP,GAAYC,GAAcI,GAA+C;AACnG,QAAMG,EAAOT,EAAQC,GAAMC,CAAI,CAAC,EAAE,IAAI,YAAY,EAAE,SAASI,KAAA,gBAAAA,EAAS,QAAA,CAAS;AACnF;AAEA,eAAsBI,EAAMT,GAAYG,GAAeO,GAAkC;AACrF,QAAMC,IACFD,KAAY,wFAEVE,IADSZ,EAAK,QAAQ,8DAA8D,EAAE,KAAA,EACpE,QAAQW,CAAc,EAAE,OAAO,EAAE,SAASR,GAAO;AAKzE,SAHK,MAAMS,EAAS,UAAW,IACrBA,EAAS,MAAA,IACTZ,EAAK,QAAQW,CAAc,EAAE,OAAO,EAAE,SAASR,EAAA,CAAO,EAAE,MAAA,GACpD,MAAA;AAClB;AAEA,eAAsBU,EAClBb,GACAC,GACAa,IAAyC,CAAA,GACzCT,IAAsB,IACN;AAChB,QAAMU,IAAS,OAAOD,KAAsB,WAAWT,EAAQ,SAASS,EAAkB,QAEpFE,KADc,OAAOF,KAAsB,WAAWT,IAAUS,GAC1C;AAE5B,MAAIR;AAOJ,SANI,OAAOQ,KAAsB,WAC7BR,IAAUN,EAAK,QAAQc,CAAiB,EAAE,OAAO,EAAE,SAASb,GAAM,EAAE,MAAA,IAEpEK,IAAUP,EAAQC,GAAMC,CAAI,GAG5Bc,KACA,MAAMP,EAAOF,CAAO,EAAE,aAAa,EAAE,SAAAU,GAAS,GACvCV,MAGX,MAAME,EAAOF,CAAO,EAAE,YAAY,EAAE,SAAAU,GAAS,GACtCV;AACX;AAEA,eAAsBW,EAASjB,GAAYkB,GAA4B;AACnE,QAAMZ,IAAUN,EAAK,QAAQ,YAAYkB,CAAG,IAAI,EAAE,MAAA;AAClD,QAAMZ,EAAQ,uBAAA,GACd,MAAME,EAAOF,CAAO,EAAE,YAAA;AAC1B;AAEA,eAAsBa,EAAenB,GAAYoB,GAAyBC,GAA8B;AACpG,QAAMrB,EAAK,eAAe,GAAG;AAE7B,QAAMsB,IAAK,MADGtB,EAAK,QAAQ,YAAY,EAAE,OAAO,EAAE,SAASoB,GAAiB,EAAE,MAAA,EACvD,aAAa,KAAK;AACzC,EAAIE,IACA,MAAMtB,EAAK,QAAQ,IAAIsB,CAAE,EAAE,EAAE,MAAA,IAE7B,MAAMtB,EAAK,QAAQoB,CAAe,EAAE,MAAA,GAExC,MAAMpB,EAAK,eAAe,GAAG,GAC7B,MAAMA,EAAK,QAAQ,kBAAkB,EAAE,OAAO,EAAE,SAASqB,EAAA,CAAO,EAAE,MAAA,EAAQ,MAAA;AAC9E;AC1FA,eAAsBE,EAAqBvB,GAA2B;AAClE,QAAMA,EAAK,cAAc,MAAM;AAC3B,WAAO,kCAAkC;AAAA,EAC7C,CAAC;AACL;ACHA,eAAsBwB,EAClBxB,GACAyB,GACAC,GACa;AACb,QAAM1B,EAAK;AAAA,IACP,OAAO,EAAE,WAAA2B,GAAW,WAAAC,QAAgB;AAChC,UAAI,CAAC,OAAO;AACR,cAAM,IAAI,MAAM,kCAAkC;AAGtD,YAAM,OAAO,eAAe,MAAMD,CAAmB,EAAE,OAAOC,CAAS;AAAA,IAC3E;AAAA,IACA,EAAE,WAAWH,GAAM,WAAWC,EAAA;AAAA,EAAK;AAE3C;AAEA,eAAsBG,EAAsC7B,GAAYyB,GAA0B;AAC9F,SAAOzB,EAAK,SAAS,OAAO2B,MAAc;AACtC,QAAI,CAAC,OAAO;AACR,YAAM,IAAI,MAAM,kCAAkC;AAKtD,YAFkB,MAAM,OAAO,eAAe,MAAMA,CAAmB,EAAE,IAAA,GAExD;AAAA,EACrB,GAAGF,CAAI;AACX;AC3BA,eAAsBK,EAAmB9B,GAAYyB,GAA8B;AAC/E,EAAI,CAAC,QAAQ,IAAI,aAAa,CAAC,QAAQ,IAAI,yBAIvCA,IACA,MAAMjB,EAAOR,CAAI,EAAE,iBAAiB,GAAGyB,CAAI,QAAQ,EAAE,YAAY,YAAY,IAE7E,MAAMjB,EAAOR,CAAI,EAAE,iBAAiB,EAAE,YAAY,YAAY;AAEtE;ACTA,eAAsB+B,EAAS/B,GAA2B;AACtD,QAAMa,EAAIb,GAAM,6BAA6B,GAC7C,MAAMO,EAAQP,GAAM,+BAA+B,EAAE,SAAS,KAAO,GACrE,MAAMA,EAAK,eAAe,GAAG;AACjC;ACNA,eAAsBgC,EAAahC,GAAYiC,GAAwC;AACnF,QAAMC,IAAMlC,EAAK,IAAA,GACXmC,IAASD,MAAQ,gBAAgB,IAAI,IAAIA,CAAG,EAAE,SAAUD,KAAW;AAGzE,SAFe,MAAMjC,EAAK,QAAA,EAAU,cAAcA,CAAI,GAEzC,KAAK,8BAA8B;AAAA,IAC5C,QAAAmC;AAAA,IACA,cAAc;AAAA,EAAA,CACjB;AACL;ACTA,eAAsBC,EAAmBpC,GAA2B;AAChE,QAAMA,EAAK,cAAc,MAAM;AAC3B,WAAO,oBAAoB;AAAA,MACvB,OAAO,CAAC,EAAE,OAAAqC,GAAO,SAAAC,QAAoD;AACjE,YAAI,OAAO;AACP;AAGJ,cAAMC,IAAeF,aAAiB,QAAQA,IAAQ,IAAI,MAAMC,KAAW,OAAOD,CAAK,CAAC;AAExF,mBAAW,MAAM;AACb,gBAAME;AAAA,QACV,CAAC;AAAA,MACL;AAAA,IAAA;AAAA,EAER,CAAC,GAEDvC,EAAK,GAAG,aAAa,CAACwC,MAAc;AAChC,QAAI,CAAAA,EAAU,QAAQ,SAAS,+DAA+D;AAI9F,YAAMA;AAAA,EACV,CAAC;AACL;ACrBO,MAAMC,IAAOC,EAAK,OAEtB;AAAA,EACC,MAAM,OAAO,EAAE,MAAA1C,EAAA,GAAQ2C,MAAQ;AAC3B,UAAMP,EAAmBpC,CAAI,GAC7B,MAAM2C,EAAI3C,CAAI;AAAA,EAClB;AAAA,EACA,cAAc;AAAA,IACV,OAAO,EAAE,MAAAA,GAAM,SAAAiC,EAAA,GAAWU,MAAQ;AAC9B,YAAMX,EAAahC,GAAMiC,CAAO,GAChC,MAAMU,EAAA;AAAA,IACV;AAAA,IACA,EAAE,MAAM,GAAA;AAAA,EAAK;AAErB,CAAC;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aerogel/playwright",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/aerogel-playwright.d.ts",
|
|
9
|
+
"default": "./dist/aerogel-playwright.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"src",
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"author": "Noel De Martin",
|
|
18
|
+
"repository": "github:NoelDeMartin/aerogel",
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "vite build",
|
|
21
|
+
"lint": "noeldemartin-lint src",
|
|
22
|
+
"verify": "noeldemartin-verify"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@playwright/test": "*"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"playwright-solid": "next"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@aerogel/plugin-solid": "workspace:*",
|
|
32
|
+
"@arethetypeswrong/cli": "catalog:",
|
|
33
|
+
"@noeldemartin/scripts": "catalog:",
|
|
34
|
+
"@playwright/test": "^1.58.2",
|
|
35
|
+
"@types/node": "catalog:",
|
|
36
|
+
"eslint": "catalog:",
|
|
37
|
+
"prettier-eslint-cli": "^8.0.1",
|
|
38
|
+
"publint": "catalog:",
|
|
39
|
+
"soukai-bis": "catalog:"
|
|
40
|
+
},
|
|
41
|
+
"eslintConfig": {
|
|
42
|
+
"extends": [
|
|
43
|
+
"@noeldemartin/eslint-config-typescript"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"prettier": {
|
|
47
|
+
"printWidth": 120
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/index.ts
ADDED
package/src/lib/a11y.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { expect } from '@playwright/test';
|
|
2
|
+
import type { Locator, Page } from '@playwright/test';
|
|
3
|
+
|
|
4
|
+
export type SeeOptions = {
|
|
5
|
+
srOnly?: boolean;
|
|
6
|
+
timeout?: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type AriaInputOptions = {
|
|
10
|
+
description?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function a11yGet(page: Page, text: string): Locator {
|
|
14
|
+
return page
|
|
15
|
+
.getByText(text, { exact: false })
|
|
16
|
+
.or(page.locator(`[aria-label="${text}"]`))
|
|
17
|
+
.first();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function ariaLabel(page: Page, label: string): Locator {
|
|
21
|
+
return page.locator(`[aria-label="${label}"]`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function ariaInput(page: Page, label: string, options: AriaInputOptions = {}): Locator {
|
|
25
|
+
let locator = page.getByLabel(label, { exact: true });
|
|
26
|
+
if (options.description) {
|
|
27
|
+
locator = locator.filter({
|
|
28
|
+
has: page.locator(`[aria-description="${options.description}"], [aria-describedby]`),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return locator;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function dontSee(page: Page, text: string, options?: { timeout?: number }): Promise<void> {
|
|
35
|
+
await expect(a11yGet(page, text)).not.toBeVisible({ timeout: options?.timeout });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function press(page: Page, label: string, selector?: string): Promise<void> {
|
|
39
|
+
const targetSelector =
|
|
40
|
+
selector ?? 'button:visible, a:visible, label:visible, details:visible, [role="menuitem"]:visible';
|
|
41
|
+
const dialog = page.locator('[role="dialog"][data-state="open"]:not([aria-hidden="true"])').last();
|
|
42
|
+
const inDialog = dialog.locator(targetSelector).filter({ hasText: label });
|
|
43
|
+
const locator =
|
|
44
|
+
(await inDialog.count()) > 0
|
|
45
|
+
? inDialog.first()
|
|
46
|
+
: page.locator(targetSelector).filter({ hasText: label }).first();
|
|
47
|
+
await locator.click();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function see(
|
|
51
|
+
page: Page,
|
|
52
|
+
text: string,
|
|
53
|
+
selectorOrOptions: string | SeeOptions = {},
|
|
54
|
+
options: SeeOptions = {},
|
|
55
|
+
): Promise<Locator> {
|
|
56
|
+
const srOnly = typeof selectorOrOptions === 'string' ? options.srOnly : selectorOrOptions.srOnly;
|
|
57
|
+
const baseOptions = typeof selectorOrOptions === 'string' ? options : selectorOrOptions;
|
|
58
|
+
const timeout = baseOptions.timeout;
|
|
59
|
+
|
|
60
|
+
let locator: Locator;
|
|
61
|
+
if (typeof selectorOrOptions === 'string') {
|
|
62
|
+
locator = page.locator(selectorOrOptions).filter({ hasText: text }).first();
|
|
63
|
+
} else {
|
|
64
|
+
locator = a11yGet(page, text);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (srOnly) {
|
|
68
|
+
await expect(locator).toBeAttached({ timeout });
|
|
69
|
+
return locator;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await expect(locator).toBeVisible({ timeout });
|
|
73
|
+
return locator;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function seeImage(page: Page, alt: string): Promise<void> {
|
|
77
|
+
const locator = page.locator(`img[alt="${alt}"]`).first();
|
|
78
|
+
await locator.scrollIntoViewIfNeeded();
|
|
79
|
+
await expect(locator).toBeVisible();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function comboboxSelect(page: Page, labelOrSelector: string, value: string): Promise<void> {
|
|
83
|
+
await page.waitForTimeout(300);
|
|
84
|
+
const label = page.locator('label[for]').filter({ hasText: labelOrSelector }).first();
|
|
85
|
+
const id = await label.getAttribute('for');
|
|
86
|
+
if (id) {
|
|
87
|
+
await page.locator(`#${id}`).click();
|
|
88
|
+
} else {
|
|
89
|
+
await page.locator(labelOrSelector).click();
|
|
90
|
+
}
|
|
91
|
+
await page.waitForTimeout(300);
|
|
92
|
+
await page.locator('*[role="option"]').filter({ hasText: value }).first().click();
|
|
93
|
+
}
|
package/src/lib/index.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Page } from '@playwright/test';
|
|
2
|
+
import type { GetModelInput, ModelsRegistry } from 'soukai-bis';
|
|
3
|
+
|
|
4
|
+
export async function create<T extends keyof ModelsRegistry>(
|
|
5
|
+
page: Page,
|
|
6
|
+
name: T,
|
|
7
|
+
data: GetModelInput<ModelsRegistry[T]>,
|
|
8
|
+
): Promise<void> {
|
|
9
|
+
await page.evaluate(
|
|
10
|
+
async ({ modelName, modelData }) => {
|
|
11
|
+
if (!window.testingRuntime) {
|
|
12
|
+
throw new Error('Testing runtime is not available');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
await window.testingRuntime.model(modelName as string).create(modelData);
|
|
16
|
+
},
|
|
17
|
+
{ modelName: name, modelData: data },
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function count<T extends keyof ModelsRegistry>(page: Page, name: T): Promise<number> {
|
|
22
|
+
return page.evaluate(async (modelName) => {
|
|
23
|
+
if (!window.testingRuntime) {
|
|
24
|
+
throw new Error('Testing runtime is not available');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const instances = await window.testingRuntime.model(modelName as string).all();
|
|
28
|
+
|
|
29
|
+
return instances.length;
|
|
30
|
+
}, name);
|
|
31
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { expect } from '@playwright/test';
|
|
2
|
+
import type { Page } from '@playwright/test';
|
|
3
|
+
|
|
4
|
+
export async function matchImageSnapshot(page: Page, name?: string): Promise<void> {
|
|
5
|
+
if (!process.env.SNAPSHOTS && !process.env.PLAYWRIGHT_SNAPSHOTS) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (name) {
|
|
10
|
+
await expect(page).toHaveScreenshot(`${name}.png`, { animations: 'disabled' });
|
|
11
|
+
} else {
|
|
12
|
+
await expect(page).toHaveScreenshot({ animations: 'disabled' });
|
|
13
|
+
}
|
|
14
|
+
}
|
package/src/lib/sync.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Page } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
import { dontSee, see } from './a11y';
|
|
4
|
+
|
|
5
|
+
export async function waitSync(page: Page): Promise<void> {
|
|
6
|
+
await see(page, 'Synchronization in progress');
|
|
7
|
+
await dontSee(page, 'Synchronization in progress', { timeout: 10000 });
|
|
8
|
+
await page.waitForTimeout(100);
|
|
9
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Page } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
export async function setupErrorListener(page: Page): Promise<void> {
|
|
4
|
+
await page.addInitScript(() => {
|
|
5
|
+
window.__aerogelEvents__ = {
|
|
6
|
+
error: ({ error, message }: { error: unknown; message?: string }) => {
|
|
7
|
+
if (window.__aerogelDisableErrorHandling__) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const errorToThrow = error instanceof Error ? error : new Error(message || String(error));
|
|
12
|
+
|
|
13
|
+
setTimeout(() => {
|
|
14
|
+
throw errorToThrow;
|
|
15
|
+
});
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
page.on('pageerror', (exception) => {
|
|
21
|
+
if (exception.message.includes('ResizeObserver loop completed with undelivered notifications.')) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
throw exception;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { test as base, expect } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
import { resetStorage } from './storage';
|
|
4
|
+
import { setupErrorListener } from './errors';
|
|
5
|
+
|
|
6
|
+
export const test = base.extend<{
|
|
7
|
+
resetStorage: void;
|
|
8
|
+
}>({
|
|
9
|
+
page: async ({ page }, use) => {
|
|
10
|
+
await setupErrorListener(page);
|
|
11
|
+
await use(page);
|
|
12
|
+
},
|
|
13
|
+
resetStorage: [
|
|
14
|
+
async ({ page, baseURL }, use) => {
|
|
15
|
+
await resetStorage(page, baseURL);
|
|
16
|
+
await use();
|
|
17
|
+
},
|
|
18
|
+
{ auto: true },
|
|
19
|
+
],
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export { expect };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Page } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
export async function resetStorage(page: Page, baseURL?: string | null): Promise<void> {
|
|
4
|
+
const url = page.url();
|
|
5
|
+
const origin = url !== 'about:blank' ? new URL(url).origin : (baseURL ?? 'http://localhost:5001');
|
|
6
|
+
const client = await page.context().newCDPSession(page);
|
|
7
|
+
|
|
8
|
+
await client.send('Storage.clearDataForOrigin', {
|
|
9
|
+
origin,
|
|
10
|
+
storageTypes: 'all',
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from 'playwright-solid';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="@aerogel/plugin-solid" />
|