@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
@@ -5,6 +5,7 @@ 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
  export { PostgreSqlContainer, StartedPostgreSqlContainer } from "@testcontainers/postgresql";
8
+ export { makePostgres } from "./postgres.js";
8
9
  export { SynapseContainer, StartedSynapseContainer } from "./synapse.js";
9
- export { MatrixAuthenticationServiceContainer, StartedMatrixAuthenticationServiceContainer, } from "./mas.js";
10
+ export { MatrixAuthenticationServiceContainer, StartedMatrixAuthenticationServiceContainer, makeMas, } from "./mas.js";
10
11
  export { MailpitContainer, StartedMailpitContainer } from "./mailpit.js";
@@ -1,7 +1,8 @@
1
- import { AbstractStartedContainer, GenericContainer, type StartedTestContainer, type ExecResult } from "testcontainers";
1
+ import { AbstractStartedContainer, GenericContainer, type StartedTestContainer, type ExecResult, type StartedNetwork } from "testcontainers";
2
2
  import { type StartedPostgreSqlContainer } from "@testcontainers/postgresql";
3
3
  import { type Credentials } from "../utils/api.js";
4
4
  import type { RootConfig as MasConfig } from "./mas-config.js";
5
+ import type { Logger } from "../utils/logger.js";
5
6
  export { type MasConfig };
6
7
  /**
7
8
  * A container running the Matrix Authentication Service.
@@ -29,8 +30,9 @@ export declare class MatrixAuthenticationServiceContainer extends GenericContain
29
30
  export declare class StartedMatrixAuthenticationServiceContainer extends AbstractStartedContainer {
30
31
  readonly baseUrl: string;
31
32
  private readonly args;
33
+ readonly sharedSecret: string;
32
34
  private adminTokenPromise?;
33
- constructor(container: StartedTestContainer, baseUrl: string, args: string[]);
35
+ constructor(container: StartedTestContainer, baseUrl: string, args: string[], sharedSecret: string);
34
36
  /**
35
37
  * Retrieves a valid HS admin token
36
38
  */
@@ -55,4 +57,5 @@ export declare class StartedMatrixAuthenticationServiceContainer extends Abstrac
55
57
  */
56
58
  setThreepid(username: string, medium: string, address: string): Promise<void>;
57
59
  }
60
+ export declare function makeMas(postgres: StartedPostgreSqlContainer, network: StartedNetwork, logger: Logger, config: Partial<MasConfig>, name?: string): Promise<StartedMatrixAuthenticationServiceContainer>;
58
61
  //# sourceMappingURL=mas.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mas.d.ts","sourceRoot":"","sources":["../../src/testcontainers/mas.ts"],"names":[],"mappings":"AAOA,OAAO,EACH,wBAAwB,EACxB,gBAAgB,EAChB,KAAK,oBAAoB,EAEzB,KAAK,UAAU,EAClB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAK7E,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAKnD,OAAO,KAAK,EAAE,UAAU,IAAI,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE/D,OAAO,EAAE,KAAK,SAAS,EAAE,CAAC;AAmH1B;;;;;GAKG;AACH,qBAAa,oCAAqC,SAAQ,gBAAgB;IACtE,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiC;gBAGlD,EAAE,EAAE,0BAA0B,EAC9B,KAAK,GAAE,MAAkE;IAe7E;;;OAGG;IACI,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,IAAI;IAQnD;;OAEG;IACmB,KAAK,IAAI,OAAO,CAAC,2CAA2C,CAAC;CA0BtF;AAED;;GAEG;AACH,qBAAa,2CAA4C,SAAQ,wBAAwB;aAKjE,OAAO,EAAE,MAAM;IAC/B,OAAO,CAAC,QAAQ,CAAC,IAAI;IALzB,OAAO,CAAC,iBAAiB,CAAC,CAAkB;gBAGxC,SAAS,EAAE,oBAAoB,EACf,OAAO,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAAE;IAKnC;;OAEG;IACU,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAYhC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC;YAQ1D,kBAAkB;YAgClB,6BAA6B;YAmB7B,oBAAoB;IAoBlC;;;;;;OAMG;IACU,YAAY,CACrB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;IAIlD;;;;;OAKG;IACU,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAO7F"}
1
+ {"version":3,"file":"mas.d.ts","sourceRoot":"","sources":["../../src/testcontainers/mas.ts"],"names":[],"mappings":"AAOA,OAAO,EACH,wBAAwB,EACxB,gBAAgB,EAChB,KAAK,oBAAoB,EAEzB,KAAK,UAAU,EACf,KAAK,cAAc,EACtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAK7E,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAKnD,OAAO,KAAK,EAAE,UAAU,IAAI,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD,OAAO,EAAE,KAAK,SAAS,EAAE,CAAC;AAmH1B;;;;;GAKG;AACH,qBAAa,oCAAqC,SAAQ,gBAAgB;IACtE,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiC;gBAGlD,EAAE,EAAE,0BAA0B,EAC9B,KAAK,GAAE,MAAkE;IAgB7E;;;OAGG;IACI,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,IAAI;IAQnD;;OAEG;IACmB,KAAK,IAAI,OAAO,CAAC,2CAA2C,CAAC;CA2BtF;AAED;;GAEG;AACH,qBAAa,2CAA4C,SAAQ,wBAAwB;aAKjE,OAAO,EAAE,MAAM;IAC/B,OAAO,CAAC,QAAQ,CAAC,IAAI;aACL,YAAY,EAAE,MAAM;IANxC,OAAO,CAAC,iBAAiB,CAAC,CAAkB;gBAGxC,SAAS,EAAE,oBAAoB,EACf,OAAO,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAAE,EACf,YAAY,EAAE,MAAM;IAKxC;;OAEG;IACU,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAYhC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC;YAQ1D,kBAAkB;YAgClB,6BAA6B;YAmB7B,oBAAoB;IAoBlC;;;;;;OAMG;IACU,YAAY,CACrB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;IAIlD;;;;;OAKG;IACU,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAO7F;AAED,wBAAsB,OAAO,CACzB,QAAQ,EAAE,0BAA0B,EACpC,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,EAC1B,IAAI,SAAQ,GACb,OAAO,CAAC,2CAA2C,CAAC,CAQtD"}
@@ -132,6 +132,7 @@ export class MatrixAuthenticationServiceContainer extends GenericContainer {
132
132
  constructor(db, image = "ghcr.io/element-hq/matrix-authentication-service:latest") {
133
133
  super(image);
134
134
  const initialConfig = deepCopy(DEFAULT_CONFIG);
135
+ initialConfig.database.host = db.getHostname();
135
136
  initialConfig.database.username = db.getUsername();
136
137
  initialConfig.database.password = db.getPassword();
137
138
  this.config = initialConfig;
@@ -170,7 +171,7 @@ export class MatrixAuthenticationServiceContainer extends GenericContainer {
170
171
  content: YAML.stringify(this.config),
171
172
  },
172
173
  ]);
173
- return new StartedMatrixAuthenticationServiceContainer(await super.start(), `http://localhost:${port}`, this.args);
174
+ return new StartedMatrixAuthenticationServiceContainer(await super.start(), `http://localhost:${port}`, this.args, this.config.matrix.secret);
174
175
  }
175
176
  }
176
177
  /**
@@ -179,11 +180,13 @@ export class MatrixAuthenticationServiceContainer extends GenericContainer {
179
180
  export class StartedMatrixAuthenticationServiceContainer extends AbstractStartedContainer {
180
181
  baseUrl;
181
182
  args;
183
+ sharedSecret;
182
184
  adminTokenPromise;
183
- constructor(container, baseUrl, args) {
185
+ constructor(container, baseUrl, args, sharedSecret) {
184
186
  super(container);
185
187
  this.baseUrl = baseUrl;
186
188
  this.args = args;
189
+ this.sharedSecret = sharedSecret;
187
190
  }
188
191
  /**
189
192
  * Retrieves a valid HS admin token
@@ -265,3 +268,12 @@ export class StartedMatrixAuthenticationServiceContainer extends AbstractStarted
265
268
  await this.manage("add-email", username, address);
266
269
  }
267
270
  }
271
+ export async function makeMas(postgres, network, logger, config, name = "mas") {
272
+ const container = await new MatrixAuthenticationServiceContainer(postgres)
273
+ .withNetwork(network)
274
+ .withNetworkAliases(name)
275
+ .withLogConsumer(logger.getConsumer(name))
276
+ .withConfig(config)
277
+ .start();
278
+ return container;
279
+ }
@@ -0,0 +1,5 @@
1
+ import { type StartedPostgreSqlContainer } from "@testcontainers/postgresql";
2
+ import { type StartedNetwork } from "testcontainers";
3
+ import { type Logger } from "../utils/logger.js";
4
+ export declare function makePostgres(network: StartedNetwork, logger: Logger, name?: string): Promise<StartedPostgreSqlContainer>;
5
+ //# sourceMappingURL=postgres.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/testcontainers/postgres.ts"],"names":[],"mappings":"AAOA,OAAO,EAAuB,KAAK,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAClG,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD,wBAAsB,YAAY,CAC9B,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,MAAM,EACd,IAAI,SAAa,GAClB,OAAO,CAAC,0BAA0B,CAAC,CAuBrC"}
@@ -0,0 +1,31 @@
1
+ /*
2
+ Copyright 2026 Element Creations Ltd.
3
+
4
+ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5
+ Please see LICENSE files in the repository root for full details.
6
+ */
7
+ import { PostgreSqlContainer } from "@testcontainers/postgresql";
8
+ export async function makePostgres(network, logger, name = "postgres") {
9
+ const container = await new PostgreSqlContainer("postgres:13.3-alpine")
10
+ .withNetwork(network)
11
+ .withNetworkAliases(name)
12
+ .withLogConsumer(logger.getConsumer(name))
13
+ .withTmpFs({
14
+ "/dev/shm/pgdata/data": "",
15
+ })
16
+ .withEnvironment({
17
+ PG_DATA: "/dev/shm/pgdata/data",
18
+ })
19
+ .withCommand([
20
+ "-c",
21
+ "shared_buffers=128MB",
22
+ "-c",
23
+ `fsync=off`,
24
+ "-c",
25
+ `synchronous_commit=off`,
26
+ "-c",
27
+ "full_page_writes=off",
28
+ ])
29
+ .start();
30
+ return container;
31
+ }
@@ -153,6 +153,12 @@ declare const DEFAULT_CONFIG: {
153
153
  module: string;
154
154
  config?: Record<string, unknown>;
155
155
  }>;
156
+ matrix_authentication_service: undefined | {
157
+ enabled?: boolean;
158
+ endpoint?: string;
159
+ secret?: string | null;
160
+ secret_path?: string | null;
161
+ };
156
162
  };
157
163
  /**
158
164
  * Incomplete type describing the configuration for a Synapse homeserver
@@ -1 +1 @@
1
- {"version":3,"file":"synapse.d.ts","sourceRoot":"","sources":["../../src/testcontainers/synapse.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"synapse.d.ts","sourceRoot":"","sources":["../../src/testcontainers/synapse.ts"],"names":[],"mappings":"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"}
@@ -136,6 +136,7 @@ const DEFAULT_CONFIG = {
136
136
  },
137
137
  room_list_publication_rules: [{ action: "allow" }],
138
138
  modules: [],
139
+ matrix_authentication_service: undefined,
139
140
  };
140
141
  /**
141
142
  * A Synapse testcontainer
@@ -217,7 +218,22 @@ export class SynapseContainer extends GenericContainer {
217
218
  return this;
218
219
  }
219
220
  withMatrixAuthenticationService(mas) {
220
- this.mas = mas;
221
+ if (mas) {
222
+ this.mas = mas;
223
+ this.withConfig({
224
+ matrix_authentication_service: {
225
+ enabled: true,
226
+ endpoint: `http://${mas.getHostname()}:8080/`,
227
+ secret: mas.sharedSecret,
228
+ },
229
+ // Must be disabled when using MAS.
230
+ password_config: {
231
+ enabled: false,
232
+ },
233
+ // Must be disabled when using MAS.
234
+ enable_registration: false,
235
+ });
236
+ }
221
237
  return this;
222
238
  }
223
239
  async start() {
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@element-hq/element-web-playwright-common",
3
3
  "type": "module",
4
- "version": "2.4.0",
4
+ "version": "3.1.0",
5
5
  "license": "SEE LICENSE IN README.md",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "git+https://github.com/element-hq/element-modules.git",
9
- "directory": "packages/element-web-playwright-common"
8
+ "url": "git+https://github.com/element-hq/element-web.git",
9
+ "directory": "packages/playwright-common"
10
10
  },
11
11
  "author": "element-hq",
12
12
  "engines": {
@@ -18,15 +18,14 @@
18
18
  "playwright-screenshots": "playwright-screenshots.sh"
19
19
  },
20
20
  "scripts": {
21
- "prepare": "tsc",
22
- "lint:types": "tsc --noEmit",
23
- "lint:codestyle": "echo 'handled by lint:eslint'",
24
- "test": "echo No tests for @element-hq/element-web-playwright-common"
21
+ "prepack": "nx build:playwright",
22
+ "lint:types": "nx lint:types"
25
23
  },
26
24
  "devDependencies": {
27
- "@element-hq/element-web-module-api": "*",
25
+ "@element-hq/element-web-module-api": "workspace:*",
28
26
  "@types/lodash-es": "^4.17.12",
29
- "typescript": "^5.8.2"
27
+ "typescript": "^5.8.2",
28
+ "wait-on": "^9.0.4"
30
29
  },
31
30
  "dependencies": {
32
31
  "@axe-core/playwright": "^4.10.1",
@@ -40,7 +39,7 @@
40
39
  },
41
40
  "peerDependencies": {
42
41
  "@element-hq/element-web-module-api": "*",
43
- "@playwright/test": "^1.52.0",
44
- "playwright-core": "^1.52.0"
42
+ "@playwright/test": "catalog:",
43
+ "playwright-core": "catalog:"
45
44
  }
46
45
  }
@@ -6,137 +6,44 @@ set -e
6
6
  SCRIPT_PATH=$(readlink -f "$0")
7
7
  SCRIPT_DIR=$(dirname "$SCRIPT_PATH")
8
8
 
9
- IMAGE_NAME="element-web-playwright-common"
9
+ function build_image() {
10
+ local IMAGE_NAME="$1"
10
11
 
11
- if docker --version | grep -q podman; then docker_is_podman=1; fi
12
-
13
- build_image() {
14
- echo "Building $IMAGE_NAME image in $SCRIPT_DIR"
15
-
16
- # Check the playwright version
17
- PM=$(cat package.json | jq -r '.packageManager')
18
- if [[ $PM == "pnpm@"* ]]; then
19
- PW_VERSION=$(pnpm list @playwright/test --depth=0 --json | jq -r '.[].devDependencies["@playwright/test"].version')
20
- else
21
- PW_VERSION=$(yarn list --pattern @playwright/test --depth=0 --json --non-interactive --no-progress | jq -r '.data.trees[].name | split("@") | last')
22
- fi
23
- echo "with Playwright version $PW_VERSION"
24
-
25
- # Build image
26
- docker build -t "$IMAGE_NAME" --build-arg "PLAYWRIGHT_VERSION=$PW_VERSION" "$SCRIPT_DIR"
12
+ echo "Building $IMAGE_NAME image in $SCRIPT_DIR"
13
+ docker build -t "$IMAGE_NAME" --build-arg "PLAYWRIGHT_VERSION=${IMAGE_NAME#*:}" "$SCRIPT_DIR"
27
14
  }
28
15
 
29
- # Find the docker socket on the host
30
- case "$DOCKER_HOST" in
31
- unix://*)
32
- docker_sock="${DOCKER_HOST:7}"
33
- ;;
34
- "")
35
- docker_sock="/var/run/docker.sock"
36
- ;;
37
- *)
38
- echo "$0: unsupported DOCKER_HOST setting '${DOCKER_HOST}'" >&2
39
- exit 1;
40
- ;;
41
- esac
42
-
43
- RUN_ARGS=(
44
- --rm
45
- --network host
46
- # Pass BASE_URL and CI environment variables to the container
47
- -e BASE_URL
48
- -e CI
49
- # Bind mount the working directory into the container
50
- -v $(pwd):/work/
51
- # Bind mount the docker socket so we can run docker commands from the container
52
- -v "${docker_sock}":/var/run/docker.sock
53
- # Bind mount /tmp so we can store temporary files
54
- -v /tmp/:/tmp/
55
- -it
56
- )
57
-
58
- DEFAULT_ARGS=(--grep @screenshot)
59
- LINK_MODULES=true
60
-
61
- # Some arguments to customise behaviour so the same script / image can be
62
- # re-used for other screenshot generation.
63
- while [[ $# -gt 0 ]]; do
64
- case "$1" in
65
- # Mounts a separate node_modules directory from a docker volume in the container.
66
- # Must be used if executing something that requires native node modules
67
- # It's a volume rather than a directory because otherwise things tend to start picking up
68
- # files from it in the native environment and break.
69
- --with-node-modules)
70
- mount_param="type=volume,src=ew-docker-node-modules,dst=/work/node_modules"
71
- # podman doesn't support `volume-nocopy`
72
- if [ -z "$docker_is_podman" ]; then mount_param+=",volume-nocopy"; fi
73
- RUN_ARGS+=(--mount "${mount_param}" -e YARN_INSTALL=true)
74
- shift
75
- ;;
76
- # Disables the automatic detection & linking of node_modules which can clash with developer tooling e.g. pnpm-link
77
- --no-link-modules)
78
- LINK_MODULES=false
79
- shift
80
- ;;
81
- # Sets a different entrypoint (in which case the default arguments to the script will be ignored)
82
- --entrypoint)
83
- shift
84
- RUN_ARGS+=(--entrypoint "$1")
85
- DEFAULT_ARGS=()
86
- shift
87
- ;;
88
- *)
89
- break
90
- ;;
91
- esac
92
- done
93
-
94
- build_image
16
+ WS_PORT=3000
95
17
 
96
- if [[ $LINK_MODULES == true ]]; then
97
- # Ensure we pass all symlinked node_modules to the container
98
- pushd node_modules > /dev/null
99
- SYMLINKS=$(find . -maxdepth 2 -type l -not -path "./.bin/*")
100
- popd > /dev/null
101
- for LINK in $SYMLINKS; do
102
- TARGET=$(readlink -f "node_modules/$LINK") || true
103
- if [ -d "$TARGET" ]; then
104
- if [ -n "$docker_is_podman" ]; then
105
- echo -e "\033[31m" >&2
106
- cat <<'EOF' >&2
107
- WARNING: `node_modules` contains symlinks, and the support for this in
108
- `playwright-screenshots.sh` is broken under podman due to
109
- https://github.com/containers/podman/issues/25947.
18
+ # Check the playwright version
19
+ PW_VERSION=$(pnpm --silent -- playwright --version | awk '{print $2}')
20
+ IMAGE_NAME="ghcr.io/element-hq/element-web/playwright-server:$PW_VERSION"
110
21
 
111
- If you get errors such as 'Error: crun: creating `<path>`', then retry this
112
- having `yarn unlink`ed the relevant node modules.
113
- EOF
114
- echo -e "\033[0m" >&2
115
- fi
116
- echo "mounting linked package ${LINK:2} in container"
117
- RUN_ARGS+=( "-v" "$TARGET:/work/node_modules/${LINK:2}" )
118
- fi
119
- done
22
+ # If the image exists in the repository, pull it; otherwise, build it.
23
+ #
24
+ # (This explicit test gives the user clearer progress info than just
25
+ # `docker pull 2>/dev/null || build_image`.)
26
+ if docker manifest inspect "$IMAGE_NAME" &>/dev/null; then
27
+ docker pull "$IMAGE_NAME"
28
+ else
29
+ build_image "$IMAGE_NAME"
120
30
  fi
121
31
 
122
- # Our Playwright fixtures use Testcontainers [1], which uses a docker image
123
- # called Ryuk [2], which will clean up any dangling containers/networks/etc
124
- # after a timeout, if the parent process dies unexpectedly.
125
- #
126
- # To do this, Ryuk requires access to the docker socket, so Testcontainers
127
- # starts the Ryuk container with a bind-mount of `/var/run/docker.sock`.
128
- # However, we're going to be running Playwright (and hence Testcontainers)
129
- # itself in a container, but talking to the Docker daemon on the *host*, which
130
- # means that bind mounts will be relative to the *host* filesystem. In short,
131
- # it will try to bind-mount the *host's* `/var/run/docker.sock` rather than
132
- # that from inside the element-web-playwright-common container.
133
- #
134
- # To solve this problem, we start Ryuk ourselves (with the correct docker
135
- # socket) rather than waiting for Testcontainers to do so. Testcontainers will
136
- # find the running Ryuk instance and connect to it rather than start a new one.
137
- #
138
- # [1] https://testcontainers.com/
139
- # [2] https://github.com/testcontainers/moby-ryuk
140
- docker run -d --rm --label org.testcontainers.ryuk=true -v "${docker_sock}":/var/run/docker.sock -p 8080 --name="playwright-ryuk" testcontainers/ryuk:0.14.0
32
+ # Start the playwright-server in docker
33
+ CONTAINER=$(docker run --network=host -v /tmp:/tmp --rm -d -e PORT="$WS_PORT" "$IMAGE_NAME")
34
+ # Set up an exit trap to clean up the docker container
35
+ clean_up() {
36
+ ARG=$?
37
+ echo "Stopping playwright-server"
38
+ docker stop "$CONTAINER" > /dev/null
39
+ exit $ARG
40
+ }
41
+ trap clean_up EXIT
42
+
43
+ # Wait for playwright-server to be ready
44
+ echo "Waiting for playwright-server"
45
+ pnpm --dir "$SCRIPT_DIR" exec wait-on "tcp:$WS_PORT"
141
46
 
142
- docker run "${RUN_ARGS[@]}" "$IMAGE_NAME" "${DEFAULT_ARGS[@]}" "$@"
47
+ # Run the test we were given, setting PW_TEST_CONNECT_WS_ENDPOINT accordingly
48
+ echo "Running '$@'"
49
+ PW_TEST_CONNECT_WS_ENDPOINT="http://localhost:$WS_PORT" "$@"
package/project.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
3
+ "projectType": "library",
4
+ "root": "packages/playwright-common",
5
+ "targets": {
6
+ "build:playwright": {
7
+ "cache": true,
8
+ "command": "tsc",
9
+ "inputs": ["src"],
10
+ "outputs": ["{projectRoot}/lib"],
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"]
18
+ },
19
+ "docker:prebuild": {
20
+ "cache": true,
21
+ "command": "echo PLAYWRIGHT_VERSION=$(pnpm --silent -- playwright --version | awk '{print $2}') > .env.docker:build",
22
+ "inputs": [{ "runtime": "pnpm --silent -- playwright --version" }],
23
+ "outputs": ["{projectRoot}/.env.docker:build"],
24
+ "options": { "cwd": "packages/playwright-common" }
25
+ },
26
+ "docker:build": {
27
+ "executor": "@nx-tools/nx-container:build",
28
+ "dependsOn": ["docker:prebuild"],
29
+ "options": {
30
+ "load": true,
31
+ "engine": "docker",
32
+ "platforms": ["linux/amd64", "linux/arm64"],
33
+ "provenance": "true",
34
+ "sbom": true,
35
+ "build-args": ["PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION"],
36
+ "context": "{projectRoot}",
37
+ "metadata": {
38
+ "images": ["ghcr.io/element-hq/element-web/playwright-server"],
39
+ "tags": ["type=ref,event=branch", "type=raw,enable={{is_default_branch}},value=$PLAYWRIGHT_VERSION"]
40
+ }
41
+ }
42
+ }
43
+ }
44
+ }
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
7
7
 
8
8
  import { type MailpitClient } from "mailpit-api";
9
9
  import { Network, type StartedNetwork } from "testcontainers";
10
- import { PostgreSqlContainer, type StartedPostgreSqlContainer } from "@testcontainers/postgresql";
10
+ import { type StartedPostgreSqlContainer } from "@testcontainers/postgresql";
11
11
 
12
12
  import {
13
13
  type SynapseConfig,
@@ -22,6 +22,7 @@ import { Logger } from "../utils/logger.js";
22
22
  // We want to avoid using `mergeTests` in index.ts because it drops useful type information about the fixtures. Instead,
23
23
  // we add `axe` into our fixture suite by using its `test` as a base, so that there is a linear hierarchy.
24
24
  import { test as base } from "./axe.js";
25
+ import { makePostgres } from "../testcontainers/postgres.js";
25
26
 
26
27
  /**
27
28
  * Test-scoped fixtures available in the test
@@ -101,27 +102,7 @@ export const test = base.extend<TestFixtures, WorkerOptions & Services>({
101
102
  ],
102
103
  postgres: [
103
104
  async ({ logger, network }, use) => {
104
- const container = await new PostgreSqlContainer("postgres:13.3-alpine")
105
- .withNetwork(network)
106
- .withNetworkAliases("postgres")
107
- .withLogConsumer(logger.getConsumer("postgres"))
108
- .withTmpFs({
109
- "/dev/shm/pgdata/data": "",
110
- })
111
- .withEnvironment({
112
- PG_DATA: "/dev/shm/pgdata/data",
113
- })
114
- .withCommand([
115
- "-c",
116
- "shared_buffers=128MB",
117
- "-c",
118
- `fsync=off`,
119
- "-c",
120
- `synchronous_commit=off`,
121
- "-c",
122
- "full_page_writes=off",
123
- ])
124
- .start();
105
+ const container = await makePostgres(network, logger);
125
106
  await use(container);
126
107
  await container.stop();
127
108
  },
@@ -0,0 +1,109 @@
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
+ * Assert that a toast with the given title exists, and return it
40
+ *
41
+ * @param title - Expected title of the toast
42
+ * @param timeout - Time to retry the assertion for in milliseconds.
43
+ * Defaults to `timeout` in `TestConfig.expect`.
44
+ * @returns the Locator for the matching toast
45
+ */
46
+ public async getToast(title: string, timeout?: number): Promise<Locator> {
47
+ const toast = this.getToastIfExists(title);
48
+ await expect(toast).toBeVisible({ timeout });
49
+ return toast;
50
+ }
51
+
52
+ /**
53
+ * Find a toast with the given title, if it exists.
54
+ *
55
+ * @param title - Title of the toast.
56
+ * @returns the Locator for the matching toast, or an empty locator if it
57
+ * doesn't exist.
58
+ */
59
+ public getToastIfExists(title: string): Locator {
60
+ return this.page.locator(".mx_Toast_toast", { hasText: title }).first();
61
+ }
62
+
63
+ /**
64
+ * Accept a toast with the given title. Only works for the first toast in
65
+ * the stack.
66
+ *
67
+ * @param title - Expected title of the toast
68
+ */
69
+ public async acceptToast(title: string): Promise<void> {
70
+ const toast = await this.getToast(title);
71
+ await toast.locator('.mx_Toast_buttons button[data-kind="primary"]').click();
72
+ }
73
+ /**
74
+ * Accept a toast with the given title, if it exists. Only works for the
75
+ * first toast in the stack.
76
+ *
77
+ * @param title - Title of the toast
78
+ */
79
+ public async acceptToastIfExists(title: string): Promise<void> {
80
+ const toast = this.getToastIfExists(title).locator('.mx_Toast_buttons button[data-kind="primary"]');
81
+ if ((await toast.count()) > 0) {
82
+ await toast.click();
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Reject a toast with the given title. Only works for the first toast in
88
+ * the stack.
89
+ *
90
+ * @param title - Expected title of the toast
91
+ */
92
+ public async rejectToast(title: string): Promise<void> {
93
+ const toast = await this.getToast(title);
94
+ await toast.locator('.mx_Toast_buttons button[data-kind="secondary"]').click();
95
+ }
96
+
97
+ /**
98
+ * Reject a toast with the given title, if it exists. Only works for the
99
+ * first toast in the stack.
100
+ *
101
+ * @param title - Title of the toast
102
+ */
103
+ public async rejectToastIfExists(title: string): Promise<void> {
104
+ const toast = this.getToastIfExists(title).locator('.mx_Toast_buttons button[data-kind="secondary"]');
105
+ if ((await toast.count()) > 0) {
106
+ await toast.click();
107
+ }
108
+ }
109
+ }
@@ -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
- import { test as base } from "./services.js";
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. */