@element-hq/element-web-playwright-common 2.4.0 → 3.1.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.
Files changed (50) hide show
  1. package/Dockerfile +14 -8
  2. package/README.md +2 -2
  3. package/docker-entrypoint.sh +1 -8
  4. package/lib/expect/axe.d.ts +1 -1
  5. package/lib/expect/axe.d.ts.map +1 -1
  6. package/lib/expect/screenshot.d.ts +1 -1
  7. package/lib/expect/screenshot.d.ts.map +1 -1
  8. package/lib/fixtures/axe.d.ts +2 -2
  9. package/lib/fixtures/axe.d.ts.map +1 -1
  10. package/lib/fixtures/services.d.ts.map +1 -1
  11. package/lib/fixtures/services.js +2 -22
  12. package/lib/fixtures/toasts.d.ts +64 -0
  13. package/lib/fixtures/toasts.d.ts.map +1 -0
  14. package/lib/fixtures/toasts.js +97 -0
  15. package/lib/fixtures/user.d.ts +13 -2
  16. package/lib/fixtures/user.d.ts.map +1 -1
  17. package/lib/fixtures/user.js +4 -1
  18. package/lib/flaky-reporter.d.ts +24 -0
  19. package/lib/flaky-reporter.d.ts.map +1 -0
  20. package/lib/flaky-reporter.js +153 -0
  21. package/lib/index.d.ts +11 -0
  22. package/lib/index.d.ts.map +1 -1
  23. package/lib/stale-screenshot-reporter.d.ts +1 -0
  24. package/lib/stale-screenshot-reporter.d.ts.map +1 -1
  25. package/lib/stale-screenshot-reporter.js +9 -4
  26. package/lib/testcontainers/index.d.ts +2 -1
  27. package/lib/testcontainers/index.d.ts.map +1 -1
  28. package/lib/testcontainers/index.js +2 -1
  29. package/lib/testcontainers/mas.d.ts +5 -2
  30. package/lib/testcontainers/mas.d.ts.map +1 -1
  31. package/lib/testcontainers/mas.js +14 -2
  32. package/lib/testcontainers/postgres.d.ts +5 -0
  33. package/lib/testcontainers/postgres.d.ts.map +1 -0
  34. package/lib/testcontainers/postgres.js +31 -0
  35. package/lib/testcontainers/synapse.d.ts +6 -0
  36. package/lib/testcontainers/synapse.d.ts.map +1 -1
  37. package/lib/testcontainers/synapse.js +17 -1
  38. package/package.json +10 -11
  39. package/playwright-screenshots.sh +33 -126
  40. package/project.json +44 -0
  41. package/src/fixtures/services.ts +3 -22
  42. package/src/fixtures/toasts.ts +109 -0
  43. package/src/fixtures/user.ts +4 -1
  44. package/src/flaky-reporter.ts +188 -0
  45. package/src/stale-screenshot-reporter.ts +9 -5
  46. package/src/testcontainers/index.ts +2 -0
  47. package/src/testcontainers/mas.ts +21 -0
  48. package/src/testcontainers/postgres.ts +40 -0
  49. package/src/testcontainers/synapse.ts +24 -1
  50. package/tsconfig.json +8 -3
package/Dockerfile CHANGED
@@ -1,18 +1,24 @@
1
1
  ARG PLAYWRIGHT_VERSION
2
2
  FROM mcr.microsoft.com/playwright:v${PLAYWRIGHT_VERSION}-noble
3
+ # Expose the argument to this build stage
4
+ ARG PLAYWRIGHT_VERSION
3
5
 
4
6
  WORKDIR /work
5
7
 
6
8
  # fonts-dejavu is needed for the same RTL rendering as on CI
7
9
  RUN apt-get update && apt-get -y install docker.io fonts-dejavu
8
-
9
- # Set up corepack
10
- RUN corepack enable
11
- ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0
12
-
13
- # Add environment variable so consumers can skip developer-centric scripts
14
- ENV PLAYWRIGHT_COMMON_DOCKER=1
10
+ # Install the matching playwright runtime, the docker image only includes browsers
11
+ RUN npm i -g playwright@${PLAYWRIGHT_VERSION}
15
12
 
16
13
  COPY docker-entrypoint.sh /docker-entrypoint.sh
17
14
 
18
- ENTRYPOINT ["/docker-entrypoint.sh"]
15
+ # We use `docker-init` as PID 1, which means that the container shuts down correctly on SIGTERM.
16
+ #
17
+ # (The problem is that PID 1 doesn't get default signal handlers, and
18
+ # playwright server doesn't register a SIGTERM handler, so if that ends up as
19
+ # PID 1, then it ignores SIGTERM. Likewise bash doesn't set a SIGTERM handler by default.
20
+ #
21
+ # The easiest solution is to use docker-init, which is in fact `tini` (https://github.com/krallin/tini).
22
+ #
23
+ # See https://github.com/krallin/tini/issues/8#issuecomment-146135930 for a good explanation of all this.)
24
+ ENTRYPOINT ["/usr/bin/docker-init", "/docker-entrypoint.sh"]
package/README.md CHANGED
@@ -18,9 +18,9 @@ The API is versioned using semver, with the major version incremented for breaki
18
18
 
19
19
  ## Copyright & License
20
20
 
21
- Copyright (c) 2025 New Vector Ltd
21
+ Copyright (c) 2026 Element Creations Ltd
22
22
 
23
- This software is multi licensed by New Vector Ltd (Element). It can be used either:
23
+ This software is multi licensed by Element Creations Ltd (Element). It can be used either:
24
24
 
25
25
  (1) for free under the terms of the GNU Affero General Public License (as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version); OR
26
26
 
@@ -1,9 +1,2 @@
1
1
  #!/bin/bash
2
-
3
- set -e
4
-
5
- if [[ "$YARN_INSTALL" == "true" ]]; then
6
- yarn install --frozen-lockfile
7
- fi
8
-
9
- npx playwright test --update-snapshots --reporter line "$@"
2
+ exec /usr/bin/playwright run-server --port "$PORT" --host 0.0.0.0
@@ -7,5 +7,5 @@ export type Expectations = {
7
7
  */
8
8
  toHaveNoViolations: (this: ExpectMatcherState, receiver: AxeBuilder) => Promise<MatcherReturnType>;
9
9
  };
10
- export declare const expect: import("@playwright/test").Expect<Expectations>;
10
+ export declare const expect: import("playwright/test").Expect<Expectations>;
11
11
  //# sourceMappingURL=axe.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"axe.d.ts","sourceRoot":"","sources":["../../src/expect/axe.ts"],"names":[],"mappings":"AAQA,OAAO,EAA8B,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE/G,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,MAAM,MAAM,YAAY,GAAG;IACvB;;;OAGG;IACH,kBAAkB,EAAE,CAAC,IAAI,EAAE,kBAAkB,EAAE,QAAQ,EAAE,UAAU,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;CACtG,CAAC;AAEF,eAAO,MAAM,MAAM,iDAgBjB,CAAC"}
1
+ {"version":3,"file":"axe.d.ts","sourceRoot":"","sources":["../../src/expect/axe.ts"],"names":[],"mappings":"AAQA,OAAO,EAA8B,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE/G,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,MAAM,MAAM,YAAY,GAAG;IACvB;;;OAGG;IACH,kBAAkB,EAAE,CAAC,IAAI,EAAE,kBAAkB,EAAE,QAAQ,EAAE,UAAU,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;CACtG,CAAC;AAEF,eAAO,MAAM,MAAM,gDAgBjB,CAAC"}
@@ -9,5 +9,5 @@ export type Expectations = {
9
9
  * Provides an upgrade to the `toHaveScreenshot` expectation.
10
10
  * Unfortunately, we can't just extend the existing `toHaveScreenshot` expectation
11
11
  */
12
- export declare const expect: import("@playwright/test").Expect<Expectations>;
12
+ export declare const expect: import("playwright/test").Expect<Expectations>;
13
13
  //# sourceMappingURL=screenshot.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../src/expect/screenshot.ts"],"names":[],"mappings":"AAQA,OAAO,EAIH,KAAK,kBAAkB,EACvB,KAAK,OAAO,EACZ,KAAK,IAAI,EACT,KAAK,qCAAqC,EAC1C,KAAK,iBAAiB,EACzB,MAAM,kBAAkB,CAAC;AAa1B,MAAM,WAAW,wBAAyB,SAAQ,qCAAqC;IACnF,GAAG,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,YAAY,GAAG;IACvB,iBAAiB,EAAE,CACf,IAAI,EAAE,kBAAkB,EACxB,QAAQ,EAAE,IAAI,GAAG,OAAO,EACxB,IAAI,EAAE,GAAG,MAAM,MAAM,EACrB,OAAO,CAAC,EAAE,wBAAwB,KACjC,OAAO,CAAC,iBAAiB,CAAC,CAAC;CACnC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,MAAM,iDA+BjB,CAAC"}
1
+ {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../src/expect/screenshot.ts"],"names":[],"mappings":"AAQA,OAAO,EAIH,KAAK,kBAAkB,EACvB,KAAK,OAAO,EACZ,KAAK,IAAI,EACT,KAAK,qCAAqC,EAC1C,KAAK,iBAAiB,EACzB,MAAM,kBAAkB,CAAC;AAa1B,MAAM,WAAW,wBAAyB,SAAQ,qCAAqC;IACnF,GAAG,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,YAAY,GAAG;IACvB,iBAAiB,EAAE,CACf,IAAI,EAAE,kBAAkB,EACxB,QAAQ,EAAE,IAAI,GAAG,OAAO,EACxB,IAAI,EAAE,GAAG,MAAM,MAAM,EACrB,OAAO,CAAC,EAAE,wBAAwB,KACjC,OAAO,CAAC,iBAAiB,CAAC,CAAC;CACnC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,MAAM,gDA+BjB,CAAC"}
@@ -1,8 +1,8 @@
1
1
  import { AxeBuilder } from "@axe-core/playwright";
2
- export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & {
2
+ export declare const test: import("playwright/test").TestType<import("playwright/test").PlaywrightTestArgs & import("playwright/test").PlaywrightTestOptions & {
3
3
  /**
4
4
  * AxeBuilder instance for the current page
5
5
  */
6
6
  axe: AxeBuilder;
7
- }, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
7
+ }, import("playwright/test").PlaywrightWorkerArgs & import("playwright/test").PlaywrightWorkerOptions>;
8
8
  //# sourceMappingURL=axe.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"axe.d.ts","sourceRoot":"","sources":["../../src/fixtures/axe.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAIlD,eAAO,MAAM,IAAI;IACb;;OAEG;SACE,UAAU;wGAMjB,CAAC"}
1
+ {"version":3,"file":"axe.d.ts","sourceRoot":"","sources":["../../src/fixtures/axe.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAIlD,eAAO,MAAM,IAAI;IACb;;OAEG;SACE,UAAU;sGAMjB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../../src/fixtures/services.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAW,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAuB,KAAK,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAElG,OAAO,EACH,KAAK,aAAa,EAElB,KAAK,2CAA2C,EAChD,KAAK,mBAAmB,EACxB,KAAK,0BAA0B,EAE/B,KAAK,uBAAuB,EAC/B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAK5C;;GAEG;AACH,MAAM,WAAW,YAAY;IACzB;;;OAGG;IACH,aAAa,EAAE,aAAa,CAAC;CAChC;AAED,MAAM,WAAW,aAAa;IAC1B;;OAEG;IACH,aAAa,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACrB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,OAAO,EAAE,cAAc,CAAC;IAExB;;OAEG;IACH,QAAQ,EAAE,0BAA0B,CAAC;IAErC;;OAEG;IACH,OAAO,EAAE,uBAAuB,CAAC;IAEjC;;OAEG;IACH,WAAW,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC1C;;OAEG;IACH,UAAU,EAAE,0BAA0B,CAAC;IAEvC;;;OAGG;IACH,GAAG,CAAC,EAAE,2CAA2C,CAAC;CACrD;AAED,eAAO,MAAM,IAAI;;gJA0Gf,CAAC"}
1
+ {"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../../src/fixtures/services.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAW,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,KAAK,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAE7E,OAAO,EACH,KAAK,aAAa,EAElB,KAAK,2CAA2C,EAChD,KAAK,mBAAmB,EACxB,KAAK,0BAA0B,EAE/B,KAAK,uBAAuB,EAC/B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAM5C;;GAEG;AACH,MAAM,WAAW,YAAY;IACzB;;;OAGG;IACH,aAAa,EAAE,aAAa,CAAC;CAChC;AAED,MAAM,WAAW,aAAa;IAC1B;;OAEG;IACH,aAAa,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACrB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,OAAO,EAAE,cAAc,CAAC;IAExB;;OAEG;IACH,QAAQ,EAAE,0BAA0B,CAAC;IAErC;;OAEG;IACH,OAAO,EAAE,uBAAuB,CAAC;IAEjC;;OAEG;IACH,WAAW,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC1C;;OAEG;IACH,UAAU,EAAE,0BAA0B,CAAC;IAEvC;;;OAGG;IACH,GAAG,CAAC,EAAE,2CAA2C,CAAC;CACrD;AAED,eAAO,MAAM,IAAI;;gJAsFf,CAAC"}
@@ -5,12 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5
5
  Please see LICENSE files in the repository root for full details.
6
6
  */
7
7
  import { Network } from "testcontainers";
8
- import { PostgreSqlContainer } from "@testcontainers/postgresql";
9
8
  import { SynapseContainer, MailpitContainer, } from "../testcontainers/index.js";
10
9
  import { Logger } from "../utils/logger.js";
11
10
  // We want to avoid using `mergeTests` in index.ts because it drops useful type information about the fixtures. Instead,
12
11
  // we add `axe` into our fixture suite by using its `test` as a base, so that there is a linear hierarchy.
13
12
  import { test as base } from "./axe.js";
13
+ import { makePostgres } from "../testcontainers/postgres.js";
14
14
  export const test = base.extend({
15
15
  logger: [
16
16
  // eslint-disable-next-line no-empty-pattern
@@ -31,27 +31,7 @@ export const test = base.extend({
31
31
  ],
32
32
  postgres: [
33
33
  async ({ logger, network }, use) => {
34
- const container = await new PostgreSqlContainer("postgres:13.3-alpine")
35
- .withNetwork(network)
36
- .withNetworkAliases("postgres")
37
- .withLogConsumer(logger.getConsumer("postgres"))
38
- .withTmpFs({
39
- "/dev/shm/pgdata/data": "",
40
- })
41
- .withEnvironment({
42
- PG_DATA: "/dev/shm/pgdata/data",
43
- })
44
- .withCommand([
45
- "-c",
46
- "shared_buffers=128MB",
47
- "-c",
48
- `fsync=off`,
49
- "-c",
50
- `synchronous_commit=off`,
51
- "-c",
52
- "full_page_writes=off",
53
- ])
54
- .start();
34
+ const container = await makePostgres(network, logger);
55
35
  await use(container);
56
36
  await container.stop();
57
37
  },
@@ -0,0 +1,64 @@
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
+ * Assert that a toast with the given title exists, and return it
19
+ *
20
+ * @param title - Expected title of the toast
21
+ * @param timeout - Time to retry the assertion for in milliseconds.
22
+ * Defaults to `timeout` in `TestConfig.expect`.
23
+ * @returns the Locator for the matching toast
24
+ */
25
+ getToast(title: string, timeout?: number): Promise<Locator>;
26
+ /**
27
+ * Find a toast with the given title, if it exists.
28
+ *
29
+ * @param title - Title of the toast.
30
+ * @returns the Locator for the matching toast, or an empty locator if it
31
+ * doesn't exist.
32
+ */
33
+ getToastIfExists(title: string): Locator;
34
+ /**
35
+ * Accept a toast with the given title. Only works for the first toast in
36
+ * the stack.
37
+ *
38
+ * @param title - Expected title of the toast
39
+ */
40
+ acceptToast(title: string): Promise<void>;
41
+ /**
42
+ * Accept a toast with the given title, if it exists. Only works for the
43
+ * first toast in the stack.
44
+ *
45
+ * @param title - Title of the toast
46
+ */
47
+ acceptToastIfExists(title: string): Promise<void>;
48
+ /**
49
+ * Reject a toast with the given title. Only works for the first toast in
50
+ * the stack.
51
+ *
52
+ * @param title - Expected title of the toast
53
+ */
54
+ rejectToast(title: string): Promise<void>;
55
+ /**
56
+ * Reject a toast with the given title, if it exists. Only works for the
57
+ * first toast in the stack.
58
+ *
59
+ * @param title - Title of the toast
60
+ */
61
+ rejectToastIfExists(title: string): Promise<void>;
62
+ }
63
+ export {};
64
+ //# 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;;;;;;;OAOG;IACU,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAMxE;;;;;;OAMG;IACI,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAI/C;;;;;OAKG;IACU,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItD;;;;;OAKG;IACU,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO9D;;;;;OAKG;IACU,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtD;;;;;OAKG;IACU,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAMjE"}
@@ -0,0 +1,97 @@
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
+ /**
31
+ * Assert that a toast with the given title exists, and return it
32
+ *
33
+ * @param title - Expected title of the toast
34
+ * @param timeout - Time to retry the assertion for in milliseconds.
35
+ * Defaults to `timeout` in `TestConfig.expect`.
36
+ * @returns the Locator for the matching toast
37
+ */
38
+ async getToast(title, timeout) {
39
+ const toast = this.getToastIfExists(title);
40
+ await expect(toast).toBeVisible({ timeout });
41
+ return toast;
42
+ }
43
+ /**
44
+ * Find a toast with the given title, if it exists.
45
+ *
46
+ * @param title - Title of the toast.
47
+ * @returns the Locator for the matching toast, or an empty locator if it
48
+ * doesn't exist.
49
+ */
50
+ getToastIfExists(title) {
51
+ return this.page.locator(".mx_Toast_toast", { hasText: title }).first();
52
+ }
53
+ /**
54
+ * Accept a toast with the given title. Only works for the first toast in
55
+ * the stack.
56
+ *
57
+ * @param title - Expected title of the toast
58
+ */
59
+ async acceptToast(title) {
60
+ const toast = await this.getToast(title);
61
+ await toast.locator('.mx_Toast_buttons button[data-kind="primary"]').click();
62
+ }
63
+ /**
64
+ * Accept a toast with the given title, if it exists. Only works for the
65
+ * first toast in the stack.
66
+ *
67
+ * @param title - Title of the toast
68
+ */
69
+ async acceptToastIfExists(title) {
70
+ const toast = this.getToastIfExists(title).locator('.mx_Toast_buttons button[data-kind="primary"]');
71
+ if ((await toast.count()) > 0) {
72
+ await toast.click();
73
+ }
74
+ }
75
+ /**
76
+ * Reject a toast with the given title. Only works for the first toast in
77
+ * the stack.
78
+ *
79
+ * @param title - Expected title of the toast
80
+ */
81
+ async rejectToast(title) {
82
+ const toast = await this.getToast(title);
83
+ await toast.locator('.mx_Toast_buttons button[data-kind="secondary"]').click();
84
+ }
85
+ /**
86
+ * Reject a toast with the given title, if it exists. Only works for the
87
+ * first toast in the stack.
88
+ *
89
+ * @param title - Title of the toast
90
+ */
91
+ async rejectToastIfExists(title) {
92
+ const toast = this.getToastIfExists(title).locator('.mx_Toast_buttons button[data-kind="secondary"]');
93
+ if ((await toast.count()) > 0) {
94
+ await toast.click();
95
+ }
96
+ }
97
+ }
@@ -2,9 +2,20 @@ import { type Page } from "@playwright/test";
2
2
  import { type Credentials } from "../utils/api.js";
3
3
  /** Adds an initScript to the given page which will populate localStorage appropriately so that Element will use the given credentials. */
4
4
  export declare function populateLocalStorageWithCredentials(page: Page, credentials: Credentials): Promise<void>;
5
- export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & {
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): Promise<import("playwright-core").Locator>;
12
+ getToastIfExists(title: string): import("playwright-core").Locator;
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
  *
@@ -31,5 +42,5 @@ export declare const test: import("@playwright/test").TestType<import("@playwrig
31
42
  * app.
32
43
  */
33
44
  user: Credentials;
34
- }, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions & import("./services.js").WorkerOptions & import("./services.js").Services>;
45
+ }, import("playwright/test").PlaywrightWorkerArgs & import("playwright/test").PlaywrightWorkerOptions & import("./services.js").WorkerOptions & import("./services.js").Services>;
35
46
  //# sourceMappingURL=user.d.ts.map
@@ -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;AAI7C,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;mLA+BnB,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"}
@@ -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
- import { test as base } from "./services.js";
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 }) => {
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Flaky test reporter, creating & updating GitHub issues
3
+ * Only intended to run from within GitHub Actions
4
+ */
5
+ import type { Reporter, TestCase } from "@playwright/test/reporter";
6
+ declare class FlakyReporter implements Reporter {
7
+ private flakes;
8
+ onTestEnd(test: TestCase): void;
9
+ /**
10
+ * Parse link header to retrieve pagination links
11
+ * @see https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api?apiVersion=2022-11-28#using-link-headers
12
+ * @param link link header from response or undefined
13
+ * @returns an empty object if link is undefined otherwise returns a map from type to link
14
+ */
15
+ private parseLinkHeader;
16
+ /**
17
+ * Fetch all flaky test issues that were updated since Jan-1-2024
18
+ * @returns A promise that resolves to a list of issues
19
+ */
20
+ getAllIssues(): Promise<any[]>;
21
+ onExit(): Promise<void>;
22
+ }
23
+ export default FlakyReporter;
24
+ //# sourceMappingURL=flaky-reporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flaky-reporter.d.ts","sourceRoot":"","sources":["../src/flaky-reporter.ts"],"names":[],"mappings":"AAQA;;;GAGG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAuBpE,cAAM,aAAc,YAAW,QAAQ;IACnC,OAAO,CAAC,MAAM,CAAiC;IAExC,SAAS,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI;IA2CtC;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAevB;;;OAGG;IACG,YAAY,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAuBvB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAuDvC;AAED,eAAe,aAAa,CAAC"}
@@ -0,0 +1,153 @@
1
+ /*
2
+ Copyright 2024 New Vector Ltd.
3
+ Copyright 2024 The Matrix.org Foundation C.I.C.
4
+
5
+ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
6
+ Please see LICENSE files in the repository root for full details.
7
+ */
8
+ const REPO = "element-hq/element-web";
9
+ const LABEL = "Z-Flaky-Test";
10
+ const ISSUE_TITLE_PREFIX = "Flaky playwright test: ";
11
+ const ANSI_COLOUR_REGEX = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
12
+ // We see quite a few test flakes which are caused by the app exploding
13
+ // so we have some magic strings we check the logs for to better track the flake with its cause
14
+ const SPECIAL_CASES = {
15
+ "ChunkLoadError": "ChunkLoadError",
16
+ "Unreachable code should not be executed": "Rust crypto panic",
17
+ "Out of bounds memory access": "Rust crypto memory error",
18
+ };
19
+ class FlakyReporter {
20
+ flakes = new Map();
21
+ onTestEnd(test) {
22
+ // Ignores flakes on Dendrite and Pinecone as they have their own flakes we do not track
23
+ if (["Dendrite", "Pinecone"].includes(test.parent.project().name))
24
+ return;
25
+ if (test.outcome() === "flaky") {
26
+ const failures = [];
27
+ const timedOutRuns = test.results.filter((result) => result.status === "timedOut");
28
+ const pageLogs = timedOutRuns.flatMap((result) => result.attachments.filter((attachment) => attachment.name.startsWith("page-")));
29
+ // If a test failed due to a systemic fault then the test is not flaky, the app is, record it as such.
30
+ const specialCases = Object.keys(SPECIAL_CASES).filter((log) => pageLogs.some((attachment) => attachment.name.startsWith("page-") && attachment.body?.includes(log)));
31
+ if (specialCases.length > 0) {
32
+ failures.push(...specialCases.map((specialCase) => SPECIAL_CASES[specialCase]));
33
+ }
34
+ // Check for fixtures failing to set up
35
+ const errorMessages = timedOutRuns
36
+ .map((r) => r.error?.message?.replace(ANSI_COLOUR_REGEX, ""))
37
+ .filter(Boolean);
38
+ for (const error of errorMessages) {
39
+ if (error.startsWith("Fixture") && error.endsWith("exceeded during setup.")) {
40
+ failures.push(error);
41
+ }
42
+ }
43
+ if (failures.length < 1) {
44
+ failures.push(`${test.location.file.split("playwright/e2e/")[1]}: ${test.title}`);
45
+ }
46
+ for (const title of failures) {
47
+ if (!this.flakes.has(title)) {
48
+ this.flakes.set(title, []);
49
+ }
50
+ this.flakes.get(title).push(test);
51
+ }
52
+ }
53
+ }
54
+ /**
55
+ * Parse link header to retrieve pagination links
56
+ * @see https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api?apiVersion=2022-11-28#using-link-headers
57
+ * @param link link header from response or undefined
58
+ * @returns an empty object if link is undefined otherwise returns a map from type to link
59
+ */
60
+ parseLinkHeader(link) {
61
+ /**
62
+ * link looks like:
63
+ * <https://api.github.com/repositories/1300192/issues?page=2>; rel="prev", <https://api.github.com/repositories/1300192/issues?page=4>;
64
+ */
65
+ const map = {};
66
+ if (!link)
67
+ return map;
68
+ const matches = link.matchAll(/(<(?<link>.+?)>; rel="(?<type>.+?)")/g);
69
+ for (const match of matches) {
70
+ const { link, type } = match.groups;
71
+ map[type] = link;
72
+ }
73
+ return map;
74
+ }
75
+ /**
76
+ * Fetch all flaky test issues that were updated since Jan-1-2024
77
+ * @returns A promise that resolves to a list of issues
78
+ */
79
+ async getAllIssues() {
80
+ const issues = [];
81
+ const { GITHUB_TOKEN, GITHUB_API_URL } = process.env;
82
+ // See https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#list-repository-issues
83
+ let url = `${GITHUB_API_URL}/repos/${REPO}/issues?labels=${LABEL}&state=all&per_page=100&sort=updated&since=2024-01-01`;
84
+ const headers = {
85
+ Authorization: `Bearer ${GITHUB_TOKEN}`,
86
+ Accept: "application / vnd.github + json",
87
+ };
88
+ while (url) {
89
+ // Fetch issues and add to list
90
+ const issuesResponse = await fetch(url, { headers });
91
+ const fetchedIssues = await issuesResponse.json();
92
+ issues.push(...fetchedIssues);
93
+ // Get the next link for fetching more results
94
+ const linkHeader = issuesResponse.headers.get("Link");
95
+ const parsed = this.parseLinkHeader(linkHeader);
96
+ url = parsed.next;
97
+ }
98
+ return issues;
99
+ }
100
+ async onExit() {
101
+ if (this.flakes.size === 0) {
102
+ console.log("No flakes found");
103
+ return;
104
+ }
105
+ console.log("Found flakes: ");
106
+ for (const flake of this.flakes) {
107
+ console.log(flake);
108
+ }
109
+ const { GITHUB_TOKEN, GITHUB_API_URL, GITHUB_SERVER_URL, GITHUB_REPOSITORY, GITHUB_RUN_ID } = process.env;
110
+ if (!GITHUB_TOKEN)
111
+ return;
112
+ const issues = await this.getAllIssues();
113
+ for (const [flake, results] of this.flakes) {
114
+ const title = ISSUE_TITLE_PREFIX + "`" + flake + "`";
115
+ const existingIssue = issues.find((issue) => issue.title === title);
116
+ const headers = { Authorization: `Bearer ${GITHUB_TOKEN}` };
117
+ const body = `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}`;
118
+ const labels = [LABEL, ...results.map((test) => `${LABEL}-${test.parent.project()?.name}`)];
119
+ if (existingIssue) {
120
+ console.log(`Found issue ${existingIssue.number} for ${flake}, adding comment...`);
121
+ // Ensure that the test is open
122
+ await fetch(existingIssue.url, {
123
+ method: "PATCH",
124
+ headers,
125
+ body: JSON.stringify({ state: "open" }),
126
+ });
127
+ await fetch(`${existingIssue.url}/labels`, {
128
+ method: "POST",
129
+ headers,
130
+ body: JSON.stringify({ labels }),
131
+ });
132
+ await fetch(`${existingIssue.url}/comments`, {
133
+ method: "POST",
134
+ headers,
135
+ body: JSON.stringify({ body }),
136
+ });
137
+ }
138
+ else {
139
+ console.log(`Creating new issue for ${flake}...`);
140
+ await fetch(`${GITHUB_API_URL}/repos/${REPO}/issues`, {
141
+ method: "POST",
142
+ headers,
143
+ body: JSON.stringify({
144
+ title,
145
+ body,
146
+ labels: [...labels],
147
+ }),
148
+ });
149
+ }
150
+ }
151
+ }
152
+ }
153
+ export default FlakyReporter;
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): Promise<import("playwright-core").Locator>;
48
+ getToastIfExists(title: string): import("playwright-core").Locator;
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;
@@ -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;;;;;;;kNAmBf,CAAC;AAEH,OAAO,EAAE,MAAM,EAAE,KAAK,wBAAwB,EAAE,MAAM,mBAAmB,CAAC"}
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"}
@@ -13,6 +13,7 @@ declare class StaleScreenshotReporter implements Reporter {
13
13
  onBegin(config: FullConfig): void;
14
14
  onTestEnd(test: TestCase): void;
15
15
  private error;
16
+ private checkStaleScreenshots;
16
17
  onExit(): Promise<void>;
17
18
  }
18
19
  export default StaleScreenshotReporter;
@@ -1 +1 @@
1
- {"version":3,"file":"stale-screenshot-reporter.d.ts","sourceRoot":"","sources":["../src/stale-screenshot-reporter.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACzE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;;GAGG;AACH,eAAO,MAAM,UAAU,gBAAgB,CAAC;AAExC,cAAM,uBAAwB,YAAW,QAAQ;IAC7C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IACnD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,OAAO,CAAQ;IAEhB,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAMjC,SAAS,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI;IActC,OAAO,CAAC,KAAK;IAQA,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAuCvC;AAED,eAAe,uBAAuB,CAAC"}
1
+ {"version":3,"file":"stale-screenshot-reporter.d.ts","sourceRoot":"","sources":["../src/stale-screenshot-reporter.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACzE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;;GAGG;AACH,eAAO,MAAM,UAAU,gBAAgB,CAAC;AAExC,cAAM,uBAAwB,YAAW,QAAQ;IAC7C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IACnD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,OAAO,CAAQ;IAEhB,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAMjC,SAAS,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI;IActC,OAAO,CAAC,KAAK;YAQC,qBAAqB;IAgCtB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAWvC;AAED,eAAe,uBAAuB,CAAC"}
@@ -45,10 +45,7 @@ class StaleScreenshotReporter {
45
45
  console.error(msg, file);
46
46
  this.success = false;
47
47
  }
48
- async onExit() {
49
- if (this.failing.size) {
50
- console.error(`${this.failing.size} tests failed, skipping stale screenshot reporter.`);
51
- }
48
+ async checkStaleScreenshots() {
52
49
  if (!this.snapshotRoots.size) {
53
50
  this.error("No snapshot directories found, did you set the snapshotDir in your Playwright config?", "");
54
51
  return;
@@ -73,6 +70,14 @@ class StaleScreenshotReporter {
73
70
  this.error("Stale screenshot file", screenshot);
74
71
  }
75
72
  }
73
+ }
74
+ async onExit() {
75
+ if (this.failing.size) {
76
+ this.error(`${this.failing.size} tests failed, skipping stale screenshot reporter.`, "");
77
+ }
78
+ else {
79
+ await this.checkStaleScreenshots();
80
+ }
76
81
  if (!this.success) {
77
82
  process.exit(1);
78
83
  }
@@ -1,6 +1,7 @@
1
1
  export { PostgreSqlContainer, StartedPostgreSqlContainer } from "@testcontainers/postgresql";
2
+ export { makePostgres } from "./postgres.js";
2
3
  export type { HomeserverInstance, HomeserverContainer, StartedHomeserverContainer } from "./HomeserverContainer.js";
3
4
  export { type SynapseConfig, SynapseContainer, StartedSynapseContainer } from "./synapse.js";
4
- export { type MasConfig, MatrixAuthenticationServiceContainer, StartedMatrixAuthenticationServiceContainer, } from "./mas.js";
5
+ export { type MasConfig, MatrixAuthenticationServiceContainer, StartedMatrixAuthenticationServiceContainer, makeMas, } from "./mas.js";
5
6
  export { type MailpitClient, MailpitContainer, StartedMailpitContainer } from "./mailpit.js";
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testcontainers/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAC7F,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACpH,OAAO,EAAE,KAAK,aAAa,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC7F,OAAO,EACH,KAAK,SAAS,EACd,oCAAoC,EACpC,2CAA2C,GAC9C,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,KAAK,aAAa,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testcontainers/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACpH,OAAO,EAAE,KAAK,aAAa,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC7F,OAAO,EACH,KAAK,SAAS,EACd,oCAAoC,EACpC,2CAA2C,EAC3C,OAAO,GACV,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,KAAK,aAAa,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC"}