@element-hq/element-web-playwright-common 3.0.0 → 4.0.0
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/Dockerfile +8 -1
- package/lib/fixtures/toasts.d.ts +73 -0
- package/lib/fixtures/toasts.d.ts.map +1 -0
- package/lib/fixtures/toasts.js +124 -0
- package/lib/fixtures/user.d.ts +11 -0
- package/lib/fixtures/user.d.ts.map +1 -1
- package/lib/fixtures/user.js +4 -1
- package/lib/index.d.ts +11 -0
- package/lib/index.d.ts.map +1 -1
- package/package.json +5 -5
- package/playwright-screenshots.sh +7 -4
- package/project.json +7 -1
- package/src/fixtures/toasts.ts +163 -0
- package/src/fixtures/user.ts +4 -1
- package/tsconfig.json +1 -0
package/Dockerfile
CHANGED
|
@@ -6,10 +6,17 @@ ARG PLAYWRIGHT_VERSION
|
|
|
6
6
|
WORKDIR /work
|
|
7
7
|
|
|
8
8
|
# fonts-dejavu is needed for the same RTL rendering as on CI
|
|
9
|
-
RUN apt-get update &&
|
|
9
|
+
RUN apt-get update && \
|
|
10
|
+
apt-get -y install docker.io fonts-dejavu && \
|
|
11
|
+
apt-get purge -y --auto-remove && \
|
|
12
|
+
rm -rf /var/lib/apt/lists/*
|
|
13
|
+
|
|
10
14
|
# Install the matching playwright runtime, the docker image only includes browsers
|
|
11
15
|
RUN npm i -g playwright@${PLAYWRIGHT_VERSION}
|
|
12
16
|
|
|
17
|
+
# switch to pwuser
|
|
18
|
+
USER 1001:1001
|
|
19
|
+
|
|
13
20
|
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
|
14
21
|
|
|
15
22
|
# We use `docker-init` as PID 1, which means that the container shuts down correctly on SIGTERM.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type Locator, type Page } from "@playwright/test";
|
|
2
|
+
export declare const test: import("playwright/test").TestType<import("playwright/test").PlaywrightTestArgs & import("playwright/test").PlaywrightTestOptions & {
|
|
3
|
+
axe: import("@axe-core/playwright").AxeBuilder;
|
|
4
|
+
} & import("./services.js").TestFixtures & {
|
|
5
|
+
/**
|
|
6
|
+
* Convenience functions for handling toasts.
|
|
7
|
+
*/
|
|
8
|
+
toasts: Toasts;
|
|
9
|
+
}, import("playwright/test").PlaywrightWorkerArgs & import("playwright/test").PlaywrightWorkerOptions & import("./services.js").WorkerOptions & import("./services.js").Services>;
|
|
10
|
+
declare class Toasts {
|
|
11
|
+
readonly page: Page;
|
|
12
|
+
constructor(page: Page);
|
|
13
|
+
/**
|
|
14
|
+
* Assert that no toasts exist.
|
|
15
|
+
*/
|
|
16
|
+
assertNoToasts(): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Return the toast with the supplied title. Fail or return null if it does
|
|
19
|
+
* not exist.
|
|
20
|
+
*
|
|
21
|
+
* If `required` is false, you should supply a relatively short `timeout`
|
|
22
|
+
* (e.g. 2000, meaning 2 seconds) to prevent your test taking too long.
|
|
23
|
+
*
|
|
24
|
+
* @param title - Expected title of the toast.
|
|
25
|
+
* @param timeout - Time in ms before we give up and decide the toast does
|
|
26
|
+
* not exist. If `required` is true, defaults to `timeout`
|
|
27
|
+
* in `TestConfig.expect`. Otherwise, defaults to 2000 (2
|
|
28
|
+
* seconds).
|
|
29
|
+
* @param required - If true, fail the test (throw an exception) if the
|
|
30
|
+
* toast is not visible. Otherwise, just return null if
|
|
31
|
+
* the toast is not visible.
|
|
32
|
+
* @returns the Locator for the matching toast, or null if it is not
|
|
33
|
+
* visible. (null will only be returned if `required` is false.)
|
|
34
|
+
*/
|
|
35
|
+
getToast(title: string, timeout?: number, required?: true): Promise<Locator>;
|
|
36
|
+
getToast(title: string, timeout: number | undefined, required: false): Promise<Locator | null>;
|
|
37
|
+
/**
|
|
38
|
+
* Accept the toast with the supplied title, or fail if it does not exist.
|
|
39
|
+
*
|
|
40
|
+
* Only works if this toast is at the top of the stack of toasts.
|
|
41
|
+
*
|
|
42
|
+
* @param title - Expected title of the toast.
|
|
43
|
+
*/
|
|
44
|
+
acceptToast(title: string): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Accept the toast with the supplied title, if it exists, or return after 2
|
|
47
|
+
* seconds if it is not found.
|
|
48
|
+
*
|
|
49
|
+
* Only works if this toast is at the top of the stack of toasts.
|
|
50
|
+
*
|
|
51
|
+
* @param title - Expected title of the toast.
|
|
52
|
+
*/
|
|
53
|
+
acceptToastIfExists(title: string): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Reject the toast with the supplied title, or fail if it does not exist.
|
|
56
|
+
*
|
|
57
|
+
* Only works if this toast is at the top of the stack of toasts.
|
|
58
|
+
*
|
|
59
|
+
* @param title - Expected title of the toast.
|
|
60
|
+
*/
|
|
61
|
+
rejectToast(title: string): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Reject the toast with the supplied title, if it exists, or return after 2
|
|
64
|
+
* seconds if it is not found.
|
|
65
|
+
*
|
|
66
|
+
* Only works if this toast is at the top of the stack of toasts.
|
|
67
|
+
*
|
|
68
|
+
* @param title - Expected title of the toast.
|
|
69
|
+
*/
|
|
70
|
+
rejectToastIfExists(title: string): Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
export {};
|
|
73
|
+
//# sourceMappingURL=toasts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toasts.d.ts","sourceRoot":"","sources":["../../src/fixtures/toasts.ts"],"names":[],"mappings":"AAOA,OAAO,EAAU,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAQnE,eAAO,MAAM,IAAI;;;IACb;;OAEG;YACK,MAAM;iLAMhB,CAAC;AAEH,cAAM,MAAM;aAC2B,IAAI,EAAE,IAAI;gBAAV,IAAI,EAAE,IAAI;IAE7C;;OAEG;IACU,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5C;;;;;;;;;;;;;;;;;OAiBG;IACU,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAC5E,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,KAAK,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAqB3G;;;;;;OAMG;IACU,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItD;;;;;;;OAOG;IACU,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9D;;;;;;OAMG;IACU,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItD;;;;;;;OAOG;IACU,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGjE"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Element Creations Ltd.
|
|
3
|
+
*
|
|
4
|
+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
5
|
+
* Please see LICENSE files in the repository root for full details.
|
|
6
|
+
*/
|
|
7
|
+
import { expect } from "@playwright/test";
|
|
8
|
+
// We want to avoid using `mergeTests` in index.ts because it drops useful type
|
|
9
|
+
// information about the fixtures. Instead, we add `services` into our fixture
|
|
10
|
+
// suite by using its `test` as a base, so that there is a linear hierarchy.
|
|
11
|
+
import { test as base } from "./services.js";
|
|
12
|
+
// This fixture provides convenient handling of Element Web's toasts.
|
|
13
|
+
export const test = base.extend({
|
|
14
|
+
toasts: async ({ page }, use) => {
|
|
15
|
+
const toasts = new Toasts(page);
|
|
16
|
+
await use(toasts);
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
class Toasts {
|
|
20
|
+
page;
|
|
21
|
+
constructor(page) {
|
|
22
|
+
this.page = page;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Assert that no toasts exist.
|
|
26
|
+
*/
|
|
27
|
+
async assertNoToasts() {
|
|
28
|
+
await expect(this.page.locator(".mx_Toast_toast")).not.toBeVisible();
|
|
29
|
+
}
|
|
30
|
+
async getToast(title, timeout, required = true) {
|
|
31
|
+
const toast = this.page.locator(".mx_Toast_toast", { hasText: title }).first();
|
|
32
|
+
if (required) {
|
|
33
|
+
await expect(toast).toBeVisible({ timeout });
|
|
34
|
+
return toast;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// If we don't set a timeout, waitFor will wait forever. Since
|
|
38
|
+
// required is false, we definitely don't want to wait forever.
|
|
39
|
+
timeout = timeout ?? 2000;
|
|
40
|
+
try {
|
|
41
|
+
await toast.waitFor({ state: "visible", timeout });
|
|
42
|
+
return toast;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Accept the toast with the supplied title, or fail if it does not exist.
|
|
51
|
+
*
|
|
52
|
+
* Only works if this toast is at the top of the stack of toasts.
|
|
53
|
+
*
|
|
54
|
+
* @param title - Expected title of the toast.
|
|
55
|
+
*/
|
|
56
|
+
async acceptToast(title) {
|
|
57
|
+
return await clickToastButton(this, title, "primary");
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Accept the toast with the supplied title, if it exists, or return after 2
|
|
61
|
+
* seconds if it is not found.
|
|
62
|
+
*
|
|
63
|
+
* Only works if this toast is at the top of the stack of toasts.
|
|
64
|
+
*
|
|
65
|
+
* @param title - Expected title of the toast.
|
|
66
|
+
*/
|
|
67
|
+
async acceptToastIfExists(title) {
|
|
68
|
+
return await clickToastButton(this, title, "primary", 2000, false);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Reject the toast with the supplied title, or fail if it does not exist.
|
|
72
|
+
*
|
|
73
|
+
* Only works if this toast is at the top of the stack of toasts.
|
|
74
|
+
*
|
|
75
|
+
* @param title - Expected title of the toast.
|
|
76
|
+
*/
|
|
77
|
+
async rejectToast(title) {
|
|
78
|
+
return await clickToastButton(this, title, "secondary");
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Reject the toast with the supplied title, if it exists, or return after 2
|
|
82
|
+
* seconds if it is not found.
|
|
83
|
+
*
|
|
84
|
+
* Only works if this toast is at the top of the stack of toasts.
|
|
85
|
+
*
|
|
86
|
+
* @param title - Expected title of the toast.
|
|
87
|
+
*/
|
|
88
|
+
async rejectToastIfExists(title) {
|
|
89
|
+
return await clickToastButton(this, title, "secondary", 2000, false);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Find the toast with the supplied title and click a button on it.
|
|
94
|
+
*
|
|
95
|
+
* Only works if this toast is at the top of the stack of toasts.
|
|
96
|
+
*
|
|
97
|
+
* If `required` is false, you should supply a relatively short `timeout`
|
|
98
|
+
* (e.g. 2000, meaning 2 seconds) to prevent your test taking too long.
|
|
99
|
+
*
|
|
100
|
+
* @param toasts - A Toasts instance.
|
|
101
|
+
* @param title - Expected title of the toast.
|
|
102
|
+
* @param button - Which button to click on the toast. Allowed values are
|
|
103
|
+
* "primary", which will accept the toast, or "secondary",
|
|
104
|
+
* which will reject it.
|
|
105
|
+
* @param timeout - Time in ms before we give up and decide the toast does
|
|
106
|
+
* not exist. If `required` is true, defaults to `timeout`
|
|
107
|
+
* in `TestConfig.expect`. Otherwise, defaults to 2000 (2
|
|
108
|
+
* seconds).
|
|
109
|
+
* @param required - If true, fail the test (throw an exception) if the
|
|
110
|
+
* toast is not visible. Otherwise, just return after
|
|
111
|
+
* `timeout` if the toast is not visible.
|
|
112
|
+
*/
|
|
113
|
+
async function clickToastButton(toasts, title, button, timeout, required = true) {
|
|
114
|
+
let toast;
|
|
115
|
+
if (required) {
|
|
116
|
+
toast = await toasts.getToast(title, timeout, true);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
toast = await toasts.getToast(title, timeout, false);
|
|
120
|
+
}
|
|
121
|
+
if (toast) {
|
|
122
|
+
await toast.locator(`.mx_Toast_buttons button[data-kind="${button}"]`).click();
|
|
123
|
+
}
|
|
124
|
+
}
|
package/lib/fixtures/user.d.ts
CHANGED
|
@@ -5,6 +5,17 @@ export declare function populateLocalStorageWithCredentials(page: Page, credenti
|
|
|
5
5
|
export declare const test: import("playwright/test").TestType<import("playwright/test").PlaywrightTestArgs & import("playwright/test").PlaywrightTestOptions & {
|
|
6
6
|
axe: import("@axe-core/playwright").AxeBuilder;
|
|
7
7
|
} & import("./services.js").TestFixtures & {
|
|
8
|
+
toasts: {
|
|
9
|
+
readonly page: Page;
|
|
10
|
+
assertNoToasts(): Promise<void>;
|
|
11
|
+
getToast(title: string, timeout?: number, required?: true): Promise<import("playwright-core").Locator>;
|
|
12
|
+
getToast(title: string, timeout: number | undefined, required: false): Promise<import("playwright-core").Locator | null>;
|
|
13
|
+
acceptToast(title: string): Promise<void>;
|
|
14
|
+
acceptToastIfExists(title: string): Promise<void>;
|
|
15
|
+
rejectToast(title: string): Promise<void>;
|
|
16
|
+
rejectToastIfExists(title: string): Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
} & {
|
|
8
19
|
/**
|
|
9
20
|
* The displayname to use for the user registered in {@link #credentials}.
|
|
10
21
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/fixtures/user.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/fixtures/user.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAO7C,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,0IAA0I;AAC1I,wBAAsB,mCAAmC,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,iBAuB7F;AAED,eAAO,MAAM,IAAI;;;;;;;;;;;;;;IACb;;;;;OAKG;kBACW,MAAM;IAEpB;;;OAGG;iBACU,WAAW;IAExB;;;;;;OAMG;yBACkB,IAAI;IAEzB;;;;OAIG;UACG,WAAW;iLA+BnB,CAAC"}
|
package/lib/fixtures/user.js
CHANGED
|
@@ -6,7 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
|
6
6
|
Please see LICENSE files in the repository root for full details.
|
|
7
7
|
*/
|
|
8
8
|
import { sample, uniqueId } from "lodash-es";
|
|
9
|
-
|
|
9
|
+
// We want to avoid using `mergeTests` in index.ts because it drops useful type
|
|
10
|
+
// information about the fixtures. Instead, we add `toasts` into our fixture
|
|
11
|
+
// suite by using its `test` as a base, so that there is a linear hierarchy.
|
|
12
|
+
import { test as base } from "./toasts.js";
|
|
10
13
|
/** Adds an initScript to the given page which will populate localStorage appropriately so that Element will use the given credentials. */
|
|
11
14
|
export async function populateLocalStorageWithCredentials(page, credentials) {
|
|
12
15
|
await page.addInitScript(({ credentials }) => {
|
package/lib/index.d.ts
CHANGED
|
@@ -41,6 +41,17 @@ export interface TestFixtures {
|
|
|
41
41
|
export declare const test: import("playwright/test").TestType<import("playwright/test").PlaywrightTestArgs & import("playwright/test").PlaywrightTestOptions & {
|
|
42
42
|
axe: import("@axe-core/playwright").AxeBuilder;
|
|
43
43
|
} & import("./fixtures/services.js").TestFixtures & {
|
|
44
|
+
toasts: {
|
|
45
|
+
readonly page: import("playwright-core").Page;
|
|
46
|
+
assertNoToasts(): Promise<void>;
|
|
47
|
+
getToast(title: string, timeout?: number, required?: true): Promise<import("playwright-core").Locator>;
|
|
48
|
+
getToast(title: string, timeout: number | undefined, required: false): Promise<import("playwright-core").Locator | null>;
|
|
49
|
+
acceptToast(title: string): Promise<void>;
|
|
50
|
+
acceptToastIfExists(title: string): Promise<void>;
|
|
51
|
+
rejectToast(title: string): Promise<void>;
|
|
52
|
+
rejectToastIfExists(title: string): Promise<void>;
|
|
53
|
+
};
|
|
54
|
+
} & {
|
|
44
55
|
displayName?: string;
|
|
45
56
|
credentials: import("./utils/api.js").Credentials;
|
|
46
57
|
pageWithCredentials: import("playwright-core").Page;
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,MAAM,IAAI,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAK/E,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AAEnC,OAAO,EAAE,mCAAmC,EAAE,MAAM,oBAAoB,CAAC;AAQzE,MAAM,WAAW,MAAO,SAAQ,UAAU;IACtC,qBAAqB,EAAE;QACnB,cAAc,CAAC,EAAE;YACb,QAAQ,EAAE,MAAM,CAAC;YACjB,WAAW,CAAC,EAAE,MAAM,CAAC;SACxB,CAAC;QACF,mBAAmB,CAAC,EAAE;YAClB,QAAQ,EAAE,MAAM,CAAC;YACjB,WAAW,CAAC,EAAE,MAAM,CAAC;SACxB,CAAC;KACL,CAAC;IACF,yBAAyB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpD,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAGD,eAAO,MAAM,WAAW,EAAE,OAAO,CAAC,MAAM,CAevC,CAAC;AAEF,MAAM,WAAW,YAAY;IACzB;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC;IAEpC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,eAAe,EAAE,OAAO,CAAC;IACzB;;;;;;;;OAQG;IACH,kBAAkB,EAAE,OAAO,CAAC;CAC/B;AAED,eAAO,MAAM,IAAI
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,MAAM,IAAI,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAK/E,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AAEnC,OAAO,EAAE,mCAAmC,EAAE,MAAM,oBAAoB,CAAC;AAQzE,MAAM,WAAW,MAAO,SAAQ,UAAU;IACtC,qBAAqB,EAAE;QACnB,cAAc,CAAC,EAAE;YACb,QAAQ,EAAE,MAAM,CAAC;YACjB,WAAW,CAAC,EAAE,MAAM,CAAC;SACxB,CAAC;QACF,mBAAmB,CAAC,EAAE;YAClB,QAAQ,EAAE,MAAM,CAAC;YACjB,WAAW,CAAC,EAAE,MAAM,CAAC;SACxB,CAAC;KACL,CAAC;IACF,yBAAyB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpD,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAGD,eAAO,MAAM,WAAW,EAAE,OAAO,CAAC,MAAM,CAevC,CAAC;AAEF,MAAM,WAAW,YAAY;IACzB;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC;IAEpC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,eAAe,EAAE,OAAO,CAAC;IACzB;;;;;;;;OAQG;IACH,kBAAkB,EAAE,OAAO,CAAC;CAC/B;AAED,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;kNAmBf,CAAC;AAEH,OAAO,EAAE,MAAM,EAAE,KAAK,wBAAwB,EAAE,MAAM,mBAAmB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@element-hq/element-web-playwright-common",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.0.0",
|
|
5
5
|
"license": "SEE LICENSE IN README.md",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -19,13 +19,12 @@
|
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
21
|
"prepack": "nx build:playwright",
|
|
22
|
-
"lint:types": "
|
|
22
|
+
"lint:types": "nx lint:types"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@element-hq/element-web-module-api": "
|
|
25
|
+
"@element-hq/element-web-module-api": "workspace:*",
|
|
26
26
|
"@types/lodash-es": "^4.17.12",
|
|
27
|
-
"typescript": "^
|
|
28
|
-
"wait-on": "^9.0.4"
|
|
27
|
+
"typescript": "^6.0.0"
|
|
29
28
|
},
|
|
30
29
|
"dependencies": {
|
|
31
30
|
"@axe-core/playwright": "^4.10.1",
|
|
@@ -35,6 +34,7 @@
|
|
|
35
34
|
"mailpit-api": "^1.2.0",
|
|
36
35
|
"strip-ansi": "^7.1.0",
|
|
37
36
|
"testcontainers": "^11.0.0",
|
|
37
|
+
"wait-on": "^9.0.4",
|
|
38
38
|
"yaml": "^2.7.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
@@ -9,7 +9,7 @@ SCRIPT_DIR=$(dirname "$SCRIPT_PATH")
|
|
|
9
9
|
function build_image() {
|
|
10
10
|
local IMAGE_NAME="$1"
|
|
11
11
|
|
|
12
|
-
echo "Building $IMAGE_NAME image in $SCRIPT_DIR"
|
|
12
|
+
echo "playwright-screenshots: Building $IMAGE_NAME image in $SCRIPT_DIR"
|
|
13
13
|
docker build -t "$IMAGE_NAME" --build-arg "PLAYWRIGHT_VERSION=${IMAGE_NAME#*:}" "$SCRIPT_DIR"
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -34,16 +34,19 @@ CONTAINER=$(docker run --network=host -v /tmp:/tmp --rm -d -e PORT="$WS_PORT" "$
|
|
|
34
34
|
# Set up an exit trap to clean up the docker container
|
|
35
35
|
clean_up() {
|
|
36
36
|
ARG=$?
|
|
37
|
-
echo "Stopping playwright-server"
|
|
37
|
+
echo "playwright-screenshots: Stopping playwright-server"
|
|
38
38
|
docker stop "$CONTAINER" > /dev/null
|
|
39
39
|
exit $ARG
|
|
40
40
|
}
|
|
41
41
|
trap clean_up EXIT
|
|
42
42
|
|
|
43
43
|
# Wait for playwright-server to be ready
|
|
44
|
-
echo "Waiting for playwright-server"
|
|
44
|
+
echo "playwright-screenshots: Waiting for playwright-server"
|
|
45
45
|
pnpm --dir "$SCRIPT_DIR" exec wait-on "tcp:$WS_PORT"
|
|
46
46
|
|
|
47
|
+
# Playwright seems to overwrite the last line from the console, so add an
|
|
48
|
+
# extra newline to make sure this doesn't get lost.
|
|
49
|
+
echo -e "playwright-screenshots: Running '$@'\n"
|
|
50
|
+
|
|
47
51
|
# Run the test we were given, setting PW_TEST_CONNECT_WS_ENDPOINT accordingly
|
|
48
|
-
echo "Running '$@'"
|
|
49
52
|
PW_TEST_CONNECT_WS_ENDPOINT="http://localhost:$WS_PORT" "$@"
|
package/project.json
CHANGED
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
"command": "tsc",
|
|
9
9
|
"inputs": ["src"],
|
|
10
10
|
"outputs": ["{projectRoot}/lib"],
|
|
11
|
-
"options": { "cwd": "packages/playwright-common" }
|
|
11
|
+
"options": { "cwd": "packages/playwright-common" },
|
|
12
|
+
"dependsOn": ["^build"]
|
|
13
|
+
},
|
|
14
|
+
"lint:types": {
|
|
15
|
+
"command": "pnpm exec tsc --noEmit",
|
|
16
|
+
"options": { "cwd": "packages/playwright-common" },
|
|
17
|
+
"dependsOn": ["^build"]
|
|
12
18
|
},
|
|
13
19
|
"docker:prebuild": {
|
|
14
20
|
"cache": true,
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Element Creations Ltd.
|
|
3
|
+
*
|
|
4
|
+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
5
|
+
* Please see LICENSE files in the repository root for full details.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { expect, type Locator, type Page } from "@playwright/test";
|
|
9
|
+
|
|
10
|
+
// We want to avoid using `mergeTests` in index.ts because it drops useful type
|
|
11
|
+
// information about the fixtures. Instead, we add `services` into our fixture
|
|
12
|
+
// suite by using its `test` as a base, so that there is a linear hierarchy.
|
|
13
|
+
import { test as base } from "./services.js";
|
|
14
|
+
|
|
15
|
+
// This fixture provides convenient handling of Element Web's toasts.
|
|
16
|
+
export const test = base.extend<{
|
|
17
|
+
/**
|
|
18
|
+
* Convenience functions for handling toasts.
|
|
19
|
+
*/
|
|
20
|
+
toasts: Toasts;
|
|
21
|
+
}>({
|
|
22
|
+
toasts: async ({ page }, use) => {
|
|
23
|
+
const toasts = new Toasts(page);
|
|
24
|
+
await use(toasts);
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
class Toasts {
|
|
29
|
+
public constructor(public readonly page: Page) {}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Assert that no toasts exist.
|
|
33
|
+
*/
|
|
34
|
+
public async assertNoToasts(): Promise<void> {
|
|
35
|
+
await expect(this.page.locator(".mx_Toast_toast")).not.toBeVisible();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Return the toast with the supplied title. Fail or return null if it does
|
|
40
|
+
* not exist.
|
|
41
|
+
*
|
|
42
|
+
* If `required` is false, you should supply a relatively short `timeout`
|
|
43
|
+
* (e.g. 2000, meaning 2 seconds) to prevent your test taking too long.
|
|
44
|
+
*
|
|
45
|
+
* @param title - Expected title of the toast.
|
|
46
|
+
* @param timeout - Time in ms before we give up and decide the toast does
|
|
47
|
+
* not exist. If `required` is true, defaults to `timeout`
|
|
48
|
+
* in `TestConfig.expect`. Otherwise, defaults to 2000 (2
|
|
49
|
+
* seconds).
|
|
50
|
+
* @param required - If true, fail the test (throw an exception) if the
|
|
51
|
+
* toast is not visible. Otherwise, just return null if
|
|
52
|
+
* the toast is not visible.
|
|
53
|
+
* @returns the Locator for the matching toast, or null if it is not
|
|
54
|
+
* visible. (null will only be returned if `required` is false.)
|
|
55
|
+
*/
|
|
56
|
+
public async getToast(title: string, timeout?: number, required?: true): Promise<Locator>;
|
|
57
|
+
public async getToast(title: string, timeout: number | undefined, required: false): Promise<Locator | null>;
|
|
58
|
+
public async getToast(title: string, timeout?: number, required = true): Promise<Locator | null> {
|
|
59
|
+
const toast = this.page.locator(".mx_Toast_toast", { hasText: title }).first();
|
|
60
|
+
|
|
61
|
+
if (required) {
|
|
62
|
+
await expect(toast).toBeVisible({ timeout });
|
|
63
|
+
return toast;
|
|
64
|
+
} else {
|
|
65
|
+
// If we don't set a timeout, waitFor will wait forever. Since
|
|
66
|
+
// required is false, we definitely don't want to wait forever.
|
|
67
|
+
timeout = timeout ?? 2000;
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
await toast.waitFor({ state: "visible", timeout });
|
|
71
|
+
return toast;
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Accept the toast with the supplied title, or fail if it does not exist.
|
|
80
|
+
*
|
|
81
|
+
* Only works if this toast is at the top of the stack of toasts.
|
|
82
|
+
*
|
|
83
|
+
* @param title - Expected title of the toast.
|
|
84
|
+
*/
|
|
85
|
+
public async acceptToast(title: string): Promise<void> {
|
|
86
|
+
return await clickToastButton(this, title, "primary");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Accept the toast with the supplied title, if it exists, or return after 2
|
|
91
|
+
* seconds if it is not found.
|
|
92
|
+
*
|
|
93
|
+
* Only works if this toast is at the top of the stack of toasts.
|
|
94
|
+
*
|
|
95
|
+
* @param title - Expected title of the toast.
|
|
96
|
+
*/
|
|
97
|
+
public async acceptToastIfExists(title: string): Promise<void> {
|
|
98
|
+
return await clickToastButton(this, title, "primary", 2000, false);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Reject the toast with the supplied title, or fail if it does not exist.
|
|
103
|
+
*
|
|
104
|
+
* Only works if this toast is at the top of the stack of toasts.
|
|
105
|
+
*
|
|
106
|
+
* @param title - Expected title of the toast.
|
|
107
|
+
*/
|
|
108
|
+
public async rejectToast(title: string): Promise<void> {
|
|
109
|
+
return await clickToastButton(this, title, "secondary");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Reject the toast with the supplied title, if it exists, or return after 2
|
|
114
|
+
* seconds if it is not found.
|
|
115
|
+
*
|
|
116
|
+
* Only works if this toast is at the top of the stack of toasts.
|
|
117
|
+
*
|
|
118
|
+
* @param title - Expected title of the toast.
|
|
119
|
+
*/
|
|
120
|
+
public async rejectToastIfExists(title: string): Promise<void> {
|
|
121
|
+
return await clickToastButton(this, title, "secondary", 2000, false);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Find the toast with the supplied title and click a button on it.
|
|
127
|
+
*
|
|
128
|
+
* Only works if this toast is at the top of the stack of toasts.
|
|
129
|
+
*
|
|
130
|
+
* If `required` is false, you should supply a relatively short `timeout`
|
|
131
|
+
* (e.g. 2000, meaning 2 seconds) to prevent your test taking too long.
|
|
132
|
+
*
|
|
133
|
+
* @param toasts - A Toasts instance.
|
|
134
|
+
* @param title - Expected title of the toast.
|
|
135
|
+
* @param button - Which button to click on the toast. Allowed values are
|
|
136
|
+
* "primary", which will accept the toast, or "secondary",
|
|
137
|
+
* which will reject it.
|
|
138
|
+
* @param timeout - Time in ms before we give up and decide the toast does
|
|
139
|
+
* not exist. If `required` is true, defaults to `timeout`
|
|
140
|
+
* in `TestConfig.expect`. Otherwise, defaults to 2000 (2
|
|
141
|
+
* seconds).
|
|
142
|
+
* @param required - If true, fail the test (throw an exception) if the
|
|
143
|
+
* toast is not visible. Otherwise, just return after
|
|
144
|
+
* `timeout` if the toast is not visible.
|
|
145
|
+
*/
|
|
146
|
+
async function clickToastButton(
|
|
147
|
+
toasts: Toasts,
|
|
148
|
+
title: string,
|
|
149
|
+
button: "primary" | "secondary",
|
|
150
|
+
timeout?: number,
|
|
151
|
+
required = true,
|
|
152
|
+
): Promise<void> {
|
|
153
|
+
let toast: Locator | null;
|
|
154
|
+
if (required) {
|
|
155
|
+
toast = await toasts.getToast(title, timeout, true);
|
|
156
|
+
} else {
|
|
157
|
+
toast = await toasts.getToast(title, timeout, false);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (toast) {
|
|
161
|
+
await toast.locator(`.mx_Toast_buttons button[data-kind="${button}"]`).click();
|
|
162
|
+
}
|
|
163
|
+
}
|
package/src/fixtures/user.ts
CHANGED
|
@@ -9,7 +9,10 @@ Please see LICENSE files in the repository root for full details.
|
|
|
9
9
|
import { type Page } from "@playwright/test";
|
|
10
10
|
import { sample, uniqueId } from "lodash-es";
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
// We want to avoid using `mergeTests` in index.ts because it drops useful type
|
|
13
|
+
// information about the fixtures. Instead, we add `toasts` into our fixture
|
|
14
|
+
// suite by using its `test` as a base, so that there is a linear hierarchy.
|
|
15
|
+
import { test as base } from "./toasts.js";
|
|
13
16
|
import { type Credentials } from "../utils/api.js";
|
|
14
17
|
|
|
15
18
|
/** Adds an initScript to the given page which will populate localStorage appropriately so that Element will use the given credentials. */
|