@element-hq/element-web-playwright-common 1.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 +9 -0
- package/README.md +28 -0
- package/lib/element-web-test.d.ts +26 -0
- package/lib/element-web-test.d.ts.map +1 -0
- package/lib/element-web-test.js +54 -0
- package/lib/expect/axe.d.ts +11 -0
- package/lib/expect/axe.d.ts.map +1 -0
- package/lib/expect/axe.js +22 -0
- package/lib/expect/index.d.ts +6 -0
- package/lib/expect/index.d.ts.map +1 -0
- package/lib/expect/index.js +10 -0
- package/lib/expect/screenshot.d.ts +14 -0
- package/lib/expect/screenshot.d.ts.map +1 -0
- package/lib/expect/screenshot.js +48 -0
- package/lib/expect/toHaveNoViolations.d.ts +11 -0
- package/lib/expect/toHaveNoViolations.d.ts.map +1 -0
- package/lib/expect/toHaveNoViolations.js +22 -0
- package/lib/expect/toMatchScreenshot.d.ts +13 -0
- package/lib/expect/toMatchScreenshot.d.ts.map +1 -0
- package/lib/expect/toMatchScreenshot.js +44 -0
- package/lib/expect/toPassAxeCheck.d.ts +7 -0
- package/lib/expect/toPassAxeCheck.d.ts.map +1 -0
- package/lib/expect/toPassAxeCheck.js +22 -0
- package/lib/fixtures/axe.d.ts +8 -0
- package/lib/fixtures/axe.d.ts.map +1 -0
- package/lib/fixtures/axe.js +15 -0
- package/lib/fixtures/index.d.ts +10 -0
- package/lib/fixtures/index.d.ts.map +1 -0
- package/lib/fixtures/index.js +10 -0
- package/lib/fixtures/services.d.ts +57 -0
- package/lib/fixtures/services.d.ts.map +1 -0
- package/lib/fixtures/services.js +114 -0
- package/lib/fixtures/user.d.ts +31 -0
- package/lib/fixtures/user.d.ts.map +1 -0
- package/lib/fixtures/user.js +47 -0
- package/lib/index.d.ts +35 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +54 -0
- package/lib/logger.d.ts +10 -0
- package/lib/logger.d.ts.map +1 -0
- package/lib/logger.js +59 -0
- package/lib/playwright.d.ts +2 -0
- package/lib/playwright.d.ts.map +1 -0
- package/lib/playwright.js +1 -0
- package/lib/testcontainers/HomeserverContainer.d.ts +70 -0
- package/lib/testcontainers/HomeserverContainer.d.ts.map +1 -0
- package/lib/testcontainers/HomeserverContainer.js +7 -0
- package/lib/testcontainers/index.d.ts +5 -0
- package/lib/testcontainers/index.d.ts.map +1 -0
- package/lib/testcontainers/index.js +9 -0
- package/lib/testcontainers/mailpit.d.ts +33 -0
- package/lib/testcontainers/mailpit.d.ts.map +1 -0
- package/lib/testcontainers/mailpit.js +52 -0
- package/lib/testcontainers/mas.d.ts +182 -0
- package/lib/testcontainers/mas.d.ts.map +1 -0
- package/lib/testcontainers/mas.js +311 -0
- package/lib/testcontainers/synapse.d.ts +229 -0
- package/lib/testcontainers/synapse.d.ts.map +1 -0
- package/lib/testcontainers/synapse.js +383 -0
- package/lib/utils/api.d.ts +49 -0
- package/lib/utils/api.d.ts.map +1 -0
- package/lib/utils/api.js +73 -0
- package/lib/utils/logger.d.ts +25 -0
- package/lib/utils/logger.d.ts.map +1 -0
- package/lib/utils/logger.js +74 -0
- package/lib/utils/object.d.ts +8 -0
- package/lib/utils/object.d.ts.map +1 -0
- package/lib/utils/object.js +15 -0
- package/lib/utils/port.d.ts +5 -0
- package/lib/utils/port.d.ts.map +1 -0
- package/lib/utils/port.js +20 -0
- package/lib/utils/rand.d.ts +6 -0
- package/lib/utils/rand.d.ts.map +1 -0
- package/lib/utils/rand.js +15 -0
- package/package.json +30 -0
- package/playwright-screenshots.sh +53 -0
- package/src/@types/playwright-core.d.ts +12 -0
- package/src/expect/axe.ts +37 -0
- package/src/expect/index.ts +21 -0
- package/src/expect/screenshot.ts +79 -0
- package/src/fixtures/axe.ts +22 -0
- package/src/fixtures/index.ts +15 -0
- package/src/fixtures/services.ts +188 -0
- package/src/fixtures/user.ts +93 -0
- package/src/index.ts +92 -0
- package/src/testcontainers/HomeserverContainer.ts +87 -0
- package/src/testcontainers/index.ts +15 -0
- package/src/testcontainers/mailpit.ts +62 -0
- package/src/testcontainers/mas.ts +382 -0
- package/src/testcontainers/synapse.ts +493 -0
- package/src/utils/api.ts +113 -0
- package/src/utils/logger.ts +79 -0
- package/src/utils/object.ts +16 -0
- package/src/utils/port.ts +22 -0
- package/src/utils/rand.ts +17 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2024-2025 New Vector Ltd.
|
|
3
|
+
Copyright 2023 The Matrix.org Foundation C.I.C.
|
|
4
|
+
|
|
5
|
+
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
6
|
+
Please see LICENSE files in the repository root for full details.
|
|
7
|
+
*/
|
|
8
|
+
import crypto from "node:crypto";
|
|
9
|
+
/**
|
|
10
|
+
* Generate a random base64 string of the given number of bytes.
|
|
11
|
+
* @param numBytes - The number of bytes to generate.
|
|
12
|
+
*/
|
|
13
|
+
export function randB64Bytes(numBytes) {
|
|
14
|
+
return crypto.randomBytes(numBytes).toString("base64").replace(/=*$/, "");
|
|
15
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@element-hq/element-web-playwright-common",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"license": "SEE LICENSE IN README.md",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"playwright-screenshots": "./playwright-screenshots.sh"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@types/lodash-es": "^4.17.12",
|
|
12
|
+
"typescript": "^5.8.2"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@axe-core/playwright": "^4.10.1",
|
|
16
|
+
"@testcontainers/postgresql": "^10.18.0",
|
|
17
|
+
"lodash-es": "^4.17.21",
|
|
18
|
+
"mailpit-api": "^1.2.0",
|
|
19
|
+
"strip-ansi": "^7.1.0",
|
|
20
|
+
"testcontainers": "^10.18.0",
|
|
21
|
+
"yaml": "^2.7.0"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@playwright/test": "^1.51.0",
|
|
25
|
+
"playwright-core": "^1.51.0"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"prepare": "tsc"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Handle symlinks here as we tend to be executed as an npm binary
|
|
4
|
+
SCRIPT_PATH=$(readlink -f "$0")
|
|
5
|
+
SCRIPT_DIR=$(dirname "$SCRIPT_PATH")
|
|
6
|
+
|
|
7
|
+
IMAGE_NAME="element-web-playwright-common"
|
|
8
|
+
echo "Building $IMAGE_NAME image in $SCRIPT_DIR"
|
|
9
|
+
|
|
10
|
+
# Build image
|
|
11
|
+
PW_VERSION=$(
|
|
12
|
+
yarn list \
|
|
13
|
+
--pattern @playwright/test \
|
|
14
|
+
--depth=0 \
|
|
15
|
+
--json \
|
|
16
|
+
--non-interactive \
|
|
17
|
+
--no-progress | \
|
|
18
|
+
jq -r '.data.trees[].name | split("@")[2]' \
|
|
19
|
+
)
|
|
20
|
+
echo "with Playwright version $PW_VERSION"
|
|
21
|
+
|
|
22
|
+
docker build -t "$IMAGE_NAME" --build-arg "PLAYWRIGHT_VERSION=$PW_VERSION" "$SCRIPT_DIR"
|
|
23
|
+
|
|
24
|
+
RUN_ARGS=(
|
|
25
|
+
--rm
|
|
26
|
+
--network host
|
|
27
|
+
# Pass BASE_URL and CI environment variables to the container
|
|
28
|
+
-e BASE_URL
|
|
29
|
+
-e CI
|
|
30
|
+
# Bind mount the working directory into the container
|
|
31
|
+
-v $(pwd):/work/
|
|
32
|
+
# Bind mount the docker socket so we can run docker commands from the container
|
|
33
|
+
-v /var/run/docker.sock:/var/run/docker.sock
|
|
34
|
+
# Bind mount /tmp so we can store temporary files
|
|
35
|
+
-v /tmp/:/tmp/
|
|
36
|
+
-it
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Ensure we pass all symlinked node_modules to the container
|
|
40
|
+
pushd node_modules || exit > /dev/null
|
|
41
|
+
SYMLINKS=$(find . -maxdepth 2 -type l -not -path "./.bin/*")
|
|
42
|
+
popd || exit > /dev/null
|
|
43
|
+
for LINK in $SYMLINKS; do
|
|
44
|
+
TARGET=$(readlink -f "node_modules/$LINK")
|
|
45
|
+
if [ -d "$TARGET" ]; then
|
|
46
|
+
echo "mounting linked package ${LINK:2} in container"
|
|
47
|
+
RUN_ARGS+=( "-v" "$TARGET:/work/node_modules/${LINK:2}" )
|
|
48
|
+
fi
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
DEFAULT_ARGS=(--grep @screenshot)
|
|
52
|
+
|
|
53
|
+
docker run "${RUN_ARGS[@]}" "$IMAGE_NAME" "${DEFAULT_ARGS[@]}" "$@"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2024-2025 New Vector Ltd.
|
|
3
|
+
Copyright 2024 The Matrix.org Foundation C.I.C.
|
|
4
|
+
|
|
5
|
+
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
6
|
+
Please see LICENSE files in the repository root for full details.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
declare module "playwright-core/lib/utils" {
|
|
10
|
+
// This type is not public in playwright-core utils
|
|
11
|
+
export function sanitizeForFilePath(filePath: string): string;
|
|
12
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2024-2025 New Vector Ltd.
|
|
3
|
+
Copyright 2023 The Matrix.org Foundation C.I.C.
|
|
4
|
+
|
|
5
|
+
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
6
|
+
Please see LICENSE files in the repository root for full details.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { test, expect as baseExpect, type ExpectMatcherState, type MatcherReturnType } from "@playwright/test";
|
|
10
|
+
|
|
11
|
+
import type AxeBuilder from "@axe-core/playwright";
|
|
12
|
+
|
|
13
|
+
export type Expectations = {
|
|
14
|
+
/**
|
|
15
|
+
* Assert that the given AxeBuilder instance has no violations.
|
|
16
|
+
* @param receiver - The AxeBuilder instance to check.
|
|
17
|
+
*/
|
|
18
|
+
toHaveNoViolations: (this: ExpectMatcherState, receiver: AxeBuilder) => Promise<MatcherReturnType>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const expect = baseExpect.extend<Expectations>({
|
|
22
|
+
async toHaveNoViolations(this: ExpectMatcherState, receiver: AxeBuilder) {
|
|
23
|
+
const testInfo = test.info();
|
|
24
|
+
if (!testInfo) throw new Error(`toHaveNoViolations() must be called during the test`);
|
|
25
|
+
|
|
26
|
+
const results = await receiver.analyze();
|
|
27
|
+
|
|
28
|
+
await testInfo.attach("accessibility-scan-results", {
|
|
29
|
+
body: JSON.stringify(results, null, 2),
|
|
30
|
+
contentType: "application/json",
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
baseExpect(results.violations).toEqual([]);
|
|
34
|
+
|
|
35
|
+
return { pass: true, message: (): string => "", name: "toHaveNoViolations" };
|
|
36
|
+
},
|
|
37
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 New Vector 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
|
+
|
|
8
|
+
import { mergeExpects, type Expect } from "@playwright/test";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
expect as screenshotExpectations,
|
|
12
|
+
Expectations as ScreenshotExpectations,
|
|
13
|
+
ToMatchScreenshotOptions,
|
|
14
|
+
} from "./screenshot.js";
|
|
15
|
+
import { expect as axeExpectations, Expectations as AxeExpectations } from "./axe.js";
|
|
16
|
+
|
|
17
|
+
export const expect = mergeExpects(screenshotExpectations, axeExpectations) as Expect<
|
|
18
|
+
ScreenshotExpectations & AxeExpectations
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
export type { ToMatchScreenshotOptions };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2024-2025 New Vector Ltd.
|
|
3
|
+
Copyright 2023 The Matrix.org Foundation C.I.C.
|
|
4
|
+
|
|
5
|
+
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
6
|
+
Please see LICENSE files in the repository root for full details.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
test,
|
|
11
|
+
expect as baseExpect,
|
|
12
|
+
ElementHandle,
|
|
13
|
+
type ExpectMatcherState,
|
|
14
|
+
type Locator,
|
|
15
|
+
type Page,
|
|
16
|
+
type PageAssertionsToHaveScreenshotOptions,
|
|
17
|
+
} from "@playwright/test";
|
|
18
|
+
import { type MatcherReturnType } from "playwright/types/test";
|
|
19
|
+
import { sanitizeForFilePath } from "playwright-core/lib/utils";
|
|
20
|
+
import { extname } from "node:path";
|
|
21
|
+
|
|
22
|
+
// Based on https://github.com/microsoft/playwright/blob/2b77ed4d7aafa85a600caa0b0d101b72c8437eeb/packages/playwright/src/util.ts#L206C8-L210C2
|
|
23
|
+
function sanitizeFilePathBeforeExtension(filePath: string): string {
|
|
24
|
+
const ext = extname(filePath);
|
|
25
|
+
const base = filePath.substring(0, filePath.length - ext.length);
|
|
26
|
+
return sanitizeForFilePath(base) + ext;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ToMatchScreenshotOptions extends PageAssertionsToHaveScreenshotOptions {
|
|
30
|
+
css?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type Expectations = {
|
|
34
|
+
toMatchScreenshot: (
|
|
35
|
+
this: ExpectMatcherState,
|
|
36
|
+
receiver: Page | Locator,
|
|
37
|
+
name: `${string}.png`,
|
|
38
|
+
options?: ToMatchScreenshotOptions,
|
|
39
|
+
) => Promise<MatcherReturnType>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Provides an upgrade to the `toHaveScreenshot` expectation.
|
|
44
|
+
* Unfortunately, we can't just extend the existing `toHaveScreenshot` expectation
|
|
45
|
+
*/
|
|
46
|
+
export const expect = baseExpect.extend<Expectations>({
|
|
47
|
+
async toMatchScreenshot(receiver, name, options) {
|
|
48
|
+
const testInfo = test.info();
|
|
49
|
+
if (!testInfo) throw new Error(`toMatchScreenshot() must be called during the test`);
|
|
50
|
+
|
|
51
|
+
if (!testInfo.tags.includes("@screenshot")) {
|
|
52
|
+
throw new Error("toMatchScreenshot() must be used in a test tagged with @screenshot");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const page = "page" in receiver ? receiver.page() : receiver;
|
|
56
|
+
|
|
57
|
+
let style: ElementHandle<Element> | undefined;
|
|
58
|
+
if (options?.css) {
|
|
59
|
+
// We add a custom style tag before taking screenshots
|
|
60
|
+
style = (await page.addStyleTag({
|
|
61
|
+
content: options.css,
|
|
62
|
+
})) as ElementHandle<Element>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const screenshotName = sanitizeFilePathBeforeExtension(name);
|
|
66
|
+
await baseExpect(receiver).toHaveScreenshot(screenshotName, options);
|
|
67
|
+
|
|
68
|
+
await style?.evaluate((tag) => tag.remove());
|
|
69
|
+
|
|
70
|
+
testInfo.annotations.push({
|
|
71
|
+
// `_` prefix hides it from the HTML reporter
|
|
72
|
+
type: "_screenshot",
|
|
73
|
+
// include a path relative to `playwright/snapshots/`
|
|
74
|
+
description: testInfo.snapshotPath(screenshotName).split("/playwright/snapshots/", 2)[1],
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return { pass: true, message: (): string => "", name: "toMatchScreenshot" };
|
|
78
|
+
},
|
|
79
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2024-2025 New Vector Ltd.
|
|
3
|
+
Copyright 2023 The Matrix.org Foundation C.I.C.
|
|
4
|
+
|
|
5
|
+
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
6
|
+
Please see LICENSE files in the repository root for full details.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { test as base } from "@playwright/test";
|
|
10
|
+
import AxeBuilder from "@axe-core/playwright";
|
|
11
|
+
|
|
12
|
+
export const test = base.extend<{
|
|
13
|
+
/**
|
|
14
|
+
* AxeBuilder instance for the current page
|
|
15
|
+
*/
|
|
16
|
+
axe: AxeBuilder;
|
|
17
|
+
}>({
|
|
18
|
+
axe: async ({ page }, use) => {
|
|
19
|
+
const builder = new AxeBuilder({ page });
|
|
20
|
+
await use(builder);
|
|
21
|
+
},
|
|
22
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 New Vector 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
|
+
|
|
8
|
+
import { mergeTests } from "@playwright/test";
|
|
9
|
+
|
|
10
|
+
import { test as axe } from "./axe.js";
|
|
11
|
+
import { test as user } from "./user.js";
|
|
12
|
+
|
|
13
|
+
export { type Services, type WorkerOptions } from "./services.js";
|
|
14
|
+
|
|
15
|
+
export const test = mergeTests(axe, user);
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2024-2025 New Vector 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
|
+
|
|
8
|
+
import { test as base } from "@playwright/test";
|
|
9
|
+
import { type MailpitClient } from "mailpit-api";
|
|
10
|
+
import { Network, type StartedNetwork } from "testcontainers";
|
|
11
|
+
import { PostgreSqlContainer, type StartedPostgreSqlContainer } from "@testcontainers/postgresql";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
type SynapseConfig,
|
|
15
|
+
SynapseContainer,
|
|
16
|
+
type StartedMatrixAuthenticationServiceContainer,
|
|
17
|
+
type HomeserverContainer,
|
|
18
|
+
type StartedHomeserverContainer,
|
|
19
|
+
MailpitContainer,
|
|
20
|
+
type StartedMailpitContainer,
|
|
21
|
+
} from "../testcontainers/index.js";
|
|
22
|
+
import { Logger } from "../utils/logger.js";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Test-scoped fixtures available in the test
|
|
26
|
+
*/
|
|
27
|
+
export interface TestFixtures {
|
|
28
|
+
/**
|
|
29
|
+
* The mailpit client instance for the test.
|
|
30
|
+
* This is a fresh client instance with no messages from prior tests.
|
|
31
|
+
*/
|
|
32
|
+
mailpitClient: MailpitClient;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface WorkerOptions {
|
|
36
|
+
/**
|
|
37
|
+
* The synapse configuration to use for the homeserver.
|
|
38
|
+
*/
|
|
39
|
+
synapseConfig: Partial<SynapseConfig>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Worker-scoped "service" fixtures available in the test
|
|
44
|
+
*/
|
|
45
|
+
export interface Services {
|
|
46
|
+
/**
|
|
47
|
+
* The logger instance for the worker.
|
|
48
|
+
*/
|
|
49
|
+
logger: Logger;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The started testcontainers network instance for the worker.
|
|
53
|
+
*/
|
|
54
|
+
network: StartedNetwork;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The started postgres container instance for the worker.
|
|
58
|
+
*/
|
|
59
|
+
postgres: StartedPostgreSqlContainer;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The started mailpit container instance for the worker.
|
|
63
|
+
*/
|
|
64
|
+
mailpit: StartedMailpitContainer;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The homeserver instance container to use for the worker.
|
|
68
|
+
*/
|
|
69
|
+
_homeserver: HomeserverContainer<unknown>;
|
|
70
|
+
/**
|
|
71
|
+
* The started homeserver instance container for the worker.
|
|
72
|
+
*/
|
|
73
|
+
homeserver: StartedHomeserverContainer;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The Matrix Authentication Service container instance for the worker.
|
|
77
|
+
* May be undefined if no delegated auth is in use.
|
|
78
|
+
*/
|
|
79
|
+
mas?: StartedMatrixAuthenticationServiceContainer;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const test = base.extend<TestFixtures, WorkerOptions & Services>({
|
|
83
|
+
logger: [
|
|
84
|
+
// eslint-disable-next-line no-empty-pattern
|
|
85
|
+
async ({}, use) => {
|
|
86
|
+
const logger = new Logger();
|
|
87
|
+
await use(logger);
|
|
88
|
+
},
|
|
89
|
+
{ scope: "worker" },
|
|
90
|
+
],
|
|
91
|
+
network: [
|
|
92
|
+
// eslint-disable-next-line no-empty-pattern
|
|
93
|
+
async ({}, use) => {
|
|
94
|
+
const network = await new Network().start();
|
|
95
|
+
await use(network);
|
|
96
|
+
await network.stop();
|
|
97
|
+
},
|
|
98
|
+
{ scope: "worker" },
|
|
99
|
+
],
|
|
100
|
+
postgres: [
|
|
101
|
+
async ({ logger, network }, use) => {
|
|
102
|
+
const container = await new PostgreSqlContainer()
|
|
103
|
+
.withNetwork(network)
|
|
104
|
+
.withNetworkAliases("postgres")
|
|
105
|
+
.withLogConsumer(logger.getConsumer("postgres"))
|
|
106
|
+
.withTmpFs({
|
|
107
|
+
"/dev/shm/pgdata/data": "",
|
|
108
|
+
})
|
|
109
|
+
.withEnvironment({
|
|
110
|
+
PG_DATA: "/dev/shm/pgdata/data",
|
|
111
|
+
})
|
|
112
|
+
.withCommand([
|
|
113
|
+
"-c",
|
|
114
|
+
"shared_buffers=128MB",
|
|
115
|
+
"-c",
|
|
116
|
+
`fsync=off`,
|
|
117
|
+
"-c",
|
|
118
|
+
`synchronous_commit=off`,
|
|
119
|
+
"-c",
|
|
120
|
+
"full_page_writes=off",
|
|
121
|
+
])
|
|
122
|
+
.start();
|
|
123
|
+
await use(container);
|
|
124
|
+
await container.stop();
|
|
125
|
+
},
|
|
126
|
+
{ scope: "worker" },
|
|
127
|
+
],
|
|
128
|
+
|
|
129
|
+
mailpit: [
|
|
130
|
+
async ({ logger, network }, use) => {
|
|
131
|
+
const container = await new MailpitContainer()
|
|
132
|
+
.withNetwork(network)
|
|
133
|
+
.withNetworkAliases("mailpit")
|
|
134
|
+
.withLogConsumer(logger.getConsumer("mailpit"))
|
|
135
|
+
.start();
|
|
136
|
+
await use(container);
|
|
137
|
+
await container.stop();
|
|
138
|
+
},
|
|
139
|
+
{ scope: "worker" },
|
|
140
|
+
],
|
|
141
|
+
mailpitClient: async ({ mailpit: container }, use) => {
|
|
142
|
+
await container.client.deleteMessages();
|
|
143
|
+
await use(container.client);
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
synapseConfig: [{}, { scope: "worker" }],
|
|
147
|
+
_homeserver: [
|
|
148
|
+
async ({ logger }, use) => {
|
|
149
|
+
const container = new SynapseContainer().withLogConsumer(logger.getConsumer("synapse"));
|
|
150
|
+
await use(container);
|
|
151
|
+
},
|
|
152
|
+
{ scope: "worker" },
|
|
153
|
+
],
|
|
154
|
+
homeserver: [
|
|
155
|
+
async ({ logger, network, _homeserver: homeserver, synapseConfig, mas }, use) => {
|
|
156
|
+
if (homeserver instanceof SynapseContainer) {
|
|
157
|
+
homeserver.withConfig(synapseConfig);
|
|
158
|
+
}
|
|
159
|
+
const container = await homeserver
|
|
160
|
+
.withNetwork(network)
|
|
161
|
+
.withNetworkAliases("homeserver")
|
|
162
|
+
.withLogConsumer(logger.getConsumer("homeserver"))
|
|
163
|
+
.withMatrixAuthenticationService(mas)
|
|
164
|
+
.start();
|
|
165
|
+
|
|
166
|
+
await use(container);
|
|
167
|
+
await container.stop();
|
|
168
|
+
},
|
|
169
|
+
{ scope: "worker" },
|
|
170
|
+
],
|
|
171
|
+
mas: [
|
|
172
|
+
// eslint-disable-next-line no-empty-pattern
|
|
173
|
+
async ({}, use) => {
|
|
174
|
+
// we stub the mas fixture to allow `homeserver` to depend on it to ensure
|
|
175
|
+
// when it is specified by `masHomeserver` it is started before the homeserver
|
|
176
|
+
await use(undefined);
|
|
177
|
+
},
|
|
178
|
+
{ scope: "worker" },
|
|
179
|
+
],
|
|
180
|
+
|
|
181
|
+
context: async ({ logger, context, request, homeserver }, use, testInfo) => {
|
|
182
|
+
homeserver.setRequest(request);
|
|
183
|
+
await logger.onTestStarted(context);
|
|
184
|
+
await use(context);
|
|
185
|
+
await logger.onTestFinished(testInfo);
|
|
186
|
+
await homeserver.onTestFinished(testInfo);
|
|
187
|
+
},
|
|
188
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2024-2025 New Vector Ltd.
|
|
3
|
+
Copyright 2023 The Matrix.org Foundation C.I.C.
|
|
4
|
+
|
|
5
|
+
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
6
|
+
Please see LICENSE files in the repository root for full details.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { type Page } from "@playwright/test";
|
|
10
|
+
import { sample, uniqueId } from "lodash-es";
|
|
11
|
+
|
|
12
|
+
import { test as base } from "./services.js";
|
|
13
|
+
import { Credentials } from "../utils/api.js";
|
|
14
|
+
|
|
15
|
+
export const test = base.extend<{
|
|
16
|
+
/**
|
|
17
|
+
* The displayname to use for the user registered in {@link #credentials}.
|
|
18
|
+
*
|
|
19
|
+
* To set it, call `test.use({ displayName: "myDisplayName" })` in the test file or `describe` block.
|
|
20
|
+
* See {@link https://playwright.dev/docs/api/class-test#test-use}.
|
|
21
|
+
*/
|
|
22
|
+
displayName?: string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A test fixture which registers a test user on the {@link #homeserver} and supplies the details
|
|
26
|
+
* of the registered user.
|
|
27
|
+
*/
|
|
28
|
+
credentials: Credentials;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The same as {@link https://playwright.dev/docs/api/class-fixtures#fixtures-page|`page`},
|
|
32
|
+
* but adds an initScript which will populate localStorage with the user's details from
|
|
33
|
+
* {@link #credentials} and {@link #homeserver}.
|
|
34
|
+
*
|
|
35
|
+
* Similar to {@link #user}, but doesn't load the app.
|
|
36
|
+
*/
|
|
37
|
+
pageWithCredentials: Page;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A (rather poorly-named) test fixture which registers a user per {@link #credentials}, stores
|
|
41
|
+
* the credentials into localStorage per {@link #homeserver}, and then loads the front page of the
|
|
42
|
+
* app.
|
|
43
|
+
*/
|
|
44
|
+
user: Credentials;
|
|
45
|
+
}>({
|
|
46
|
+
displayName: undefined,
|
|
47
|
+
credentials: async ({ homeserver, displayName: testDisplayName }, use, testInfo) => {
|
|
48
|
+
const names = ["Alice", "Bob", "Charlie", "Daniel", "Eve", "Frank", "Grace", "Hannah", "Isaac", "Judy"];
|
|
49
|
+
const password = uniqueId("password_");
|
|
50
|
+
const displayName = testDisplayName ?? sample(names)!;
|
|
51
|
+
|
|
52
|
+
const credentials = await homeserver.registerUser(`user_${testInfo.testId}`, password, displayName);
|
|
53
|
+
console.log(`Registered test user ${credentials.userId} with displayname ${displayName}`);
|
|
54
|
+
|
|
55
|
+
await use({
|
|
56
|
+
...credentials,
|
|
57
|
+
displayName,
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
pageWithCredentials: async ({ page, homeserver, credentials }, use) => {
|
|
62
|
+
await page.addInitScript(
|
|
63
|
+
({ baseUrl, credentials }) => {
|
|
64
|
+
// Seed the localStorage with the required credentials
|
|
65
|
+
window.localStorage.setItem("mx_hs_url", baseUrl);
|
|
66
|
+
window.localStorage.setItem("mx_user_id", credentials.userId);
|
|
67
|
+
window.localStorage.setItem("mx_access_token", credentials.accessToken);
|
|
68
|
+
window.localStorage.setItem("mx_device_id", credentials.deviceId);
|
|
69
|
+
window.localStorage.setItem("mx_is_guest", "false");
|
|
70
|
+
window.localStorage.setItem("mx_has_pickle_key", "false");
|
|
71
|
+
window.localStorage.setItem("mx_has_access_token", "true");
|
|
72
|
+
|
|
73
|
+
window.localStorage.setItem(
|
|
74
|
+
"mx_local_settings",
|
|
75
|
+
JSON.stringify({
|
|
76
|
+
// Retain any other settings which may have already been set
|
|
77
|
+
...JSON.parse(window.localStorage.getItem("mx_local_settings") || "{}"),
|
|
78
|
+
// Ensure the language is set to a consistent value
|
|
79
|
+
language: "en",
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
},
|
|
83
|
+
{ baseUrl: homeserver.baseUrl, credentials },
|
|
84
|
+
);
|
|
85
|
+
await use(page);
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
user: async ({ pageWithCredentials: page, credentials }, use) => {
|
|
89
|
+
await page.goto("/");
|
|
90
|
+
await page.waitForSelector(".mx_MatrixChat", { timeout: 30000 });
|
|
91
|
+
await use(credentials);
|
|
92
|
+
},
|
|
93
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2024-2025 New Vector Ltd.
|
|
3
|
+
Copyright 2023 The Matrix.org Foundation C.I.C.
|
|
4
|
+
|
|
5
|
+
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
6
|
+
Please see LICENSE files in the repository root for full details.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { type Config as BaseConfig } from "@element-hq/element-web-module-api";
|
|
10
|
+
|
|
11
|
+
import { test as base } from "./fixtures/index.js";
|
|
12
|
+
|
|
13
|
+
// Enable experimental service worker support
|
|
14
|
+
// See https://playwright.dev/docs/service-workers-experimental#how-to-enable
|
|
15
|
+
process.env["PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS"] = "1";
|
|
16
|
+
|
|
17
|
+
// We extend the Module API Config interface so that all modules
|
|
18
|
+
// which use declaration merging will have their config types correctly applied.
|
|
19
|
+
export interface Config extends BaseConfig {
|
|
20
|
+
default_server_config: {
|
|
21
|
+
"m.homeserver"?: {
|
|
22
|
+
base_url: string;
|
|
23
|
+
server_name?: string;
|
|
24
|
+
};
|
|
25
|
+
"m.identity_server"?: {
|
|
26
|
+
base_url: string;
|
|
27
|
+
server_name?: string;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
setting_defaults: Record<string, unknown>;
|
|
31
|
+
map_style_url?: string;
|
|
32
|
+
features: Record<string, boolean>;
|
|
33
|
+
modules?: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// This is deliberately quite a minimal config.json, so that we can test that the default settings actually work.
|
|
37
|
+
export const CONFIG_JSON: Partial<Config> = {
|
|
38
|
+
default_server_config: {},
|
|
39
|
+
|
|
40
|
+
// The default language is set here for test consistency
|
|
41
|
+
setting_defaults: {
|
|
42
|
+
language: "en-GB",
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// the location tests want a map style url.
|
|
46
|
+
map_style_url: "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx",
|
|
47
|
+
|
|
48
|
+
features: {
|
|
49
|
+
// We don't want to go through the feature announcement during the e2e test
|
|
50
|
+
feature_release_announcement: false,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export interface TestFixtures {
|
|
55
|
+
/**
|
|
56
|
+
* The contents of the config.json to send when the client requests it.
|
|
57
|
+
*/
|
|
58
|
+
config: Partial<typeof CONFIG_JSON>;
|
|
59
|
+
|
|
60
|
+
labsFlags: string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const test = base.extend<TestFixtures>({
|
|
64
|
+
config: {}, // We merge this atop the default CONFIG_JSON in the page fixture to make extending it easier
|
|
65
|
+
labsFlags: [],
|
|
66
|
+
page: async ({ homeserver, context, page, config, labsFlags }, use) => {
|
|
67
|
+
await context.route(`http://localhost:8080/config.json*`, async (route) => {
|
|
68
|
+
const json = {
|
|
69
|
+
...CONFIG_JSON,
|
|
70
|
+
...config,
|
|
71
|
+
default_server_config: {
|
|
72
|
+
"m.homeserver": {
|
|
73
|
+
base_url: homeserver.baseUrl,
|
|
74
|
+
},
|
|
75
|
+
...config.default_server_config,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
json["features"] = {
|
|
79
|
+
...json["features"],
|
|
80
|
+
// Enable the lab features
|
|
81
|
+
...labsFlags.reduce<NonNullable<(typeof CONFIG_JSON)["features"]>>((obj, flag) => {
|
|
82
|
+
obj[flag] = true;
|
|
83
|
+
return obj;
|
|
84
|
+
}, {}),
|
|
85
|
+
};
|
|
86
|
+
await route.fulfill({ json });
|
|
87
|
+
});
|
|
88
|
+
await use(page);
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export { expect, type ToMatchScreenshotOptions } from "./expect/index.js";
|