Add a login test against Synapse to Playwright (#11913)
* Install playwright Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Add foundations for writing tests under Playwright Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * .gitignore juggling Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Add tsconfig and fix eslint rules Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Add docker & synapse plugins Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Add login.spec.ts Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Wire up fixture which sets up ElementAppPage & bakes config.json Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove launch test, it has served its purpose Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove test which has been ported to Playwright Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix test not cleaning up after itself Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Move registerUser to the Homeserver interface Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove unused fixture param Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove redundant launch test Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Add newline --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> Co-authored-by: R Midhun Suresh <hi@midhun.dev>
This commit is contained in:
parent
ecc46aeb8c
commit
52e3e0de1f
26 changed files with 1138 additions and 89 deletions
cypress/e2e/login
package.jsonplaywright
yarn.lock
|
@ -26,65 +26,6 @@ describe("Login", () => {
|
||||||
cy.stopHomeserver(homeserver);
|
cy.stopHomeserver(homeserver);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("m.login.password", () => {
|
|
||||||
const username = "user1234";
|
|
||||||
const password = "p4s5W0rD";
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.startHomeserver("consent").then((data) => {
|
|
||||||
homeserver = data;
|
|
||||||
cy.registerUser(homeserver, username, password);
|
|
||||||
cy.visit("/#/login");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("logs in with an existing account and lands on the home screen", () => {
|
|
||||||
cy.injectAxe();
|
|
||||||
|
|
||||||
// first pick the homeserver, as otherwise the user picker won't be visible
|
|
||||||
cy.findByRole("button", { name: "Edit" }).click();
|
|
||||||
cy.findByRole("textbox", { name: "Other homeserver" }).type(homeserver.baseUrl);
|
|
||||||
cy.findByRole("button", { name: "Continue" }).click();
|
|
||||||
// wait for the dialog to go away
|
|
||||||
cy.get(".mx_ServerPickerDialog").should("not.exist");
|
|
||||||
|
|
||||||
cy.get(".mx_Spinner").should("not.exist");
|
|
||||||
cy.get(".mx_ServerPicker_server").should("have.text", homeserver.baseUrl);
|
|
||||||
|
|
||||||
cy.findByRole("button", { name: "Edit" }).click();
|
|
||||||
|
|
||||||
// select the default server again
|
|
||||||
cy.get(".mx_StyledRadioButton").first().click();
|
|
||||||
cy.findByRole("button", { name: "Continue" }).click();
|
|
||||||
cy.get(".mx_ServerPickerDialog").should("not.exist");
|
|
||||||
cy.get(".mx_Spinner").should("not.exist");
|
|
||||||
// name of default server
|
|
||||||
cy.get(".mx_ServerPicker_server").should("have.text", "server.invalid");
|
|
||||||
|
|
||||||
// switch back to the custom homeserver
|
|
||||||
|
|
||||||
cy.findByRole("button", { name: "Edit" }).click();
|
|
||||||
cy.findByRole("textbox", { name: "Other homeserver" }).type(homeserver.baseUrl);
|
|
||||||
cy.findByRole("button", { name: "Continue" }).click();
|
|
||||||
// wait for the dialog to go away
|
|
||||||
cy.get(".mx_ServerPickerDialog").should("not.exist");
|
|
||||||
|
|
||||||
cy.get(".mx_Spinner").should("not.exist");
|
|
||||||
cy.get(".mx_ServerPicker_server").should("have.text", homeserver.baseUrl);
|
|
||||||
|
|
||||||
cy.findByRole("textbox", { name: "Username", timeout: 15000 }).should("be.visible");
|
|
||||||
// Disabled because flaky - see https://github.com/vector-im/element-web/issues/24688
|
|
||||||
//cy.percySnapshot("Login");
|
|
||||||
cy.checkA11y();
|
|
||||||
|
|
||||||
cy.findByRole("textbox", { name: "Username" }).type(username);
|
|
||||||
cy.findByPlaceholderText("Password").type(password);
|
|
||||||
cy.findByRole("button", { name: "Sign in" }).click();
|
|
||||||
|
|
||||||
cy.url().should("contain", "/#/home", { timeout: 30000 });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// tests for old-style SSO login, in which we exchange tokens with Synapse, and Synapse talks to an auth server
|
// tests for old-style SSO login, in which we exchange tokens with Synapse, and Synapse talks to an auth server
|
||||||
describe("SSO login", () => {
|
describe("SSO login", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
|
@ -33,6 +33,7 @@ describe("Soft logout", () => {
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
cy.stopHomeserver(homeserver);
|
cy.stopHomeserver(homeserver);
|
||||||
|
cy.task("stopOAuthServer");
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("with password user", () => {
|
describe("with password user", () => {
|
||||||
|
|
|
@ -184,6 +184,7 @@
|
||||||
"@typescript-eslint/parser": "^5.6.0",
|
"@typescript-eslint/parser": "^5.6.0",
|
||||||
"allchange": "^1.1.0",
|
"allchange": "^1.1.0",
|
||||||
"axe-core": "4.8.2",
|
"axe-core": "4.8.2",
|
||||||
|
"axe-playwright": "^1.2.3",
|
||||||
"babel-jest": "^29.0.0",
|
"babel-jest": "^29.0.0",
|
||||||
"blob-polyfill": "^7.0.0",
|
"blob-polyfill": "^7.0.0",
|
||||||
"cypress": "^12.0.0",
|
"cypress": "^12.0.0",
|
||||||
|
|
1
playwright/.gitignore
vendored
1
playwright/.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
/test-results/
|
/test-results/
|
||||||
/html-report/
|
/html-report/
|
||||||
|
/synapselogs/
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { test, expect } from "@playwright/test";
|
|
||||||
|
|
||||||
test.describe("App launch", () => {
|
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
await page.goto("/");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should launch and render the welcome view successfully", async ({ page }) => {
|
|
||||||
await page.locator("#matrixchat").waitFor();
|
|
||||||
await page.locator(".mx_Welcome").waitFor();
|
|
||||||
await expect(page).toHaveURL("http://localhost:8080/#/welcome");
|
|
||||||
});
|
|
||||||
});
|
|
78
playwright/e2e/login/login.spec.ts
Normal file
78
playwright/e2e/login/login.spec.ts
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { checkA11y, injectAxe } from "axe-playwright";
|
||||||
|
|
||||||
|
import { test, expect } from "../../element-web-test";
|
||||||
|
|
||||||
|
test.describe("Consent", () => {
|
||||||
|
test.describe("m.login.password", () => {
|
||||||
|
test.use({ startHomeserverOpts: "consent" });
|
||||||
|
|
||||||
|
const username = "user1234";
|
||||||
|
const password = "p4s5W0rD";
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page, homeserver }) => {
|
||||||
|
await homeserver.registerUser(username, password);
|
||||||
|
await page.goto("/#/login");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("logs in with an existing account and lands on the home screen", async ({ page, homeserver }) => {
|
||||||
|
await injectAxe(page);
|
||||||
|
|
||||||
|
// first pick the homeserver, as otherwise the user picker won't be visible
|
||||||
|
await page.getByRole("button", { name: "Edit" }).click();
|
||||||
|
await page.getByRole("textbox", { name: "Other homeserver" }).fill(homeserver.config.baseUrl);
|
||||||
|
await page.getByRole("button", { name: "Continue", exact: true }).click();
|
||||||
|
// wait for the dialog to go away
|
||||||
|
await expect(page.locator(".mx_ServerPickerDialog")).toHaveCount(0);
|
||||||
|
|
||||||
|
await expect(page.locator(".mx_Spinner")).toHaveCount(0);
|
||||||
|
await expect(page.locator(".mx_ServerPicker_server")).toHaveText(homeserver.config.baseUrl);
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: "Edit" }).click();
|
||||||
|
|
||||||
|
// select the default server again
|
||||||
|
await page.locator(".mx_StyledRadioButton").first().click();
|
||||||
|
await page.getByRole("button", { name: "Continue", exact: true }).click();
|
||||||
|
await expect(page.locator(".mx_ServerPickerDialog")).toHaveCount(0);
|
||||||
|
await expect(page.locator(".mx_Spinner")).toHaveCount(0);
|
||||||
|
// name of default server
|
||||||
|
await expect(page.locator(".mx_ServerPicker_server")).toHaveText("server.invalid");
|
||||||
|
|
||||||
|
// switch back to the custom homeserver
|
||||||
|
await page.getByRole("button", { name: "Edit" }).click();
|
||||||
|
await page.getByRole("textbox", { name: "Other homeserver" }).fill(homeserver.config.baseUrl);
|
||||||
|
await page.getByRole("button", { name: "Continue", exact: true }).click();
|
||||||
|
// wait for the dialog to go away
|
||||||
|
await expect(page.locator(".mx_ServerPickerDialog")).toHaveCount(0);
|
||||||
|
|
||||||
|
await expect(page.locator(".mx_Spinner")).toHaveCount(0);
|
||||||
|
await expect(page.locator(".mx_ServerPicker_server")).toHaveText(homeserver.config.baseUrl);
|
||||||
|
|
||||||
|
await expect(page.getByRole("textbox", { name: "Username" })).toBeVisible();
|
||||||
|
// Disabled because flaky - see https://github.com/vector-im/element-web/issues/24688
|
||||||
|
// cy.percySnapshot("Login");
|
||||||
|
await checkA11y(page);
|
||||||
|
|
||||||
|
await page.getByRole("textbox", { name: "Username" }).fill(username);
|
||||||
|
await page.getByPlaceholder("Password").fill(password);
|
||||||
|
await page.getByRole("button", { name: "Sign in" }).click();
|
||||||
|
|
||||||
|
await expect(page).toHaveURL(/\/#\/home$/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
64
playwright/element-web-test.ts
Normal file
64
playwright/element-web-test.ts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { test as base } from "@playwright/test";
|
||||||
|
|
||||||
|
import { HomeserverInstance, StartHomeserverOpts } from "./plugins/utils/homeserver";
|
||||||
|
import { Synapse } from "./plugins/synapse";
|
||||||
|
|
||||||
|
const CONFIG_JSON = {
|
||||||
|
// This is deliberately quite a minimal config.json, so that we can test that the default settings
|
||||||
|
// actually work.
|
||||||
|
//
|
||||||
|
// The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver.
|
||||||
|
// We point that to a guaranteed-invalid domain.
|
||||||
|
default_server_config: {
|
||||||
|
"m.homeserver": {
|
||||||
|
base_url: "https://server.invalid",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// the location tests want a map style url.
|
||||||
|
map_style_url: "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const test = base.extend<{
|
||||||
|
startHomeserverOpts: StartHomeserverOpts | string;
|
||||||
|
homeserver: HomeserverInstance;
|
||||||
|
}>({
|
||||||
|
page: async ({ context, page }, use) => {
|
||||||
|
await context.route(`http://localhost:8080/config.json*`, async (route) => {
|
||||||
|
await route.fulfill({ json: CONFIG_JSON });
|
||||||
|
});
|
||||||
|
|
||||||
|
await use(page);
|
||||||
|
},
|
||||||
|
|
||||||
|
startHomeserverOpts: "default",
|
||||||
|
homeserver: async ({ request, startHomeserverOpts: opts }, use) => {
|
||||||
|
if (typeof opts === "string") {
|
||||||
|
opts = { template: opts };
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = new Synapse(request);
|
||||||
|
await use(await server.start(opts));
|
||||||
|
await server.stop();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test.use({});
|
||||||
|
|
||||||
|
export { expect } from "@playwright/test";
|
153
playwright/plugins/docker/index.ts
Normal file
153
playwright/plugins/docker/index.ts
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as os from "os";
|
||||||
|
import * as crypto from "crypto";
|
||||||
|
import * as childProcess from "child_process";
|
||||||
|
import * as fse from "fs-extra";
|
||||||
|
|
||||||
|
export class Docker {
|
||||||
|
public id: string;
|
||||||
|
|
||||||
|
async run(opts: { image: string; containerName: string; params?: string[]; cmd?: string[] }): Promise<string> {
|
||||||
|
const userInfo = os.userInfo();
|
||||||
|
const params = opts.params ?? [];
|
||||||
|
|
||||||
|
if (params?.includes("-v") && userInfo.uid >= 0) {
|
||||||
|
// Run the docker container as our uid:gid to prevent problems with permissions.
|
||||||
|
if (await Docker.isPodman()) {
|
||||||
|
// Note: this setup is for podman rootless containers.
|
||||||
|
|
||||||
|
// In podman, run as root in the container, which maps to the current
|
||||||
|
// user on the host. This is probably the default since Synapse's
|
||||||
|
// Dockerfile doesn't specify, but we're being explicit here
|
||||||
|
// because it's important for the permissions to work.
|
||||||
|
params.push("-u", "0:0");
|
||||||
|
|
||||||
|
// Tell Synapse not to switch UID
|
||||||
|
params.push("-e", "UID=0");
|
||||||
|
params.push("-e", "GID=0");
|
||||||
|
} else {
|
||||||
|
params.push("-u", `${userInfo.uid}:${userInfo.gid}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = [
|
||||||
|
"run",
|
||||||
|
"--name",
|
||||||
|
`${opts.containerName}-${crypto.randomBytes(4).toString("hex")}`,
|
||||||
|
"-d",
|
||||||
|
"--rm",
|
||||||
|
...params,
|
||||||
|
opts.image,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (opts.cmd) args.push(...opts.cmd);
|
||||||
|
|
||||||
|
this.id = await new Promise<string>((resolve, reject) => {
|
||||||
|
childProcess.execFile("docker", args, (err, stdout) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve(stdout.trim());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(): Promise<void> {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
childProcess.execFile("docker", ["stop", this.id], (err) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exec(params: string[]): Promise<void> {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
childProcess.execFile(
|
||||||
|
"docker",
|
||||||
|
["exec", this.id, ...params],
|
||||||
|
{ encoding: "utf8" },
|
||||||
|
(err, stdout, stderr) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(stdout);
|
||||||
|
console.log(stderr);
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
rm(): Promise<void> {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
childProcess.execFile("docker", ["rm", this.id], (err) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getContainerIp(): Promise<string> {
|
||||||
|
return new Promise<string>((resolve, reject) => {
|
||||||
|
childProcess.execFile(
|
||||||
|
"docker",
|
||||||
|
["inspect", "-f", "{{ .NetworkSettings.IPAddress }}", this.id],
|
||||||
|
(err, stdout) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
else resolve(stdout.trim());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async persistLogsToFile(args: { stdoutFile?: string; stderrFile?: string }): Promise<void> {
|
||||||
|
const stdoutFile = args.stdoutFile ? await fse.open(args.stdoutFile, "w") : "ignore";
|
||||||
|
const stderrFile = args.stderrFile ? await fse.open(args.stderrFile, "w") : "ignore";
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
childProcess
|
||||||
|
.spawn("docker", ["logs", this.id], {
|
||||||
|
stdio: ["ignore", stdoutFile, stderrFile],
|
||||||
|
})
|
||||||
|
.once("close", resolve);
|
||||||
|
});
|
||||||
|
if (args.stdoutFile) await fse.close(<number>stdoutFile);
|
||||||
|
if (args.stderrFile) await fse.close(<number>stderrFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects whether the docker command is actually podman.
|
||||||
|
* To do this, it looks for "podman" in the output of "docker --help".
|
||||||
|
*/
|
||||||
|
static isPodman(): Promise<boolean> {
|
||||||
|
return new Promise<boolean>((resolve, reject) => {
|
||||||
|
childProcess.execFile("docker", ["--help"], (err, stdout) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
else resolve(stdout.toLowerCase().includes("podman"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supply the right hostname to use to talk to the host machine. On Docker this
|
||||||
|
* is "host.docker.internal" and on Podman this is "host.containers.internal".
|
||||||
|
*/
|
||||||
|
static async hostnameOfHost(): Promise<"host.containers.internal" | "host.docker.internal"> {
|
||||||
|
return (await Docker.isPodman()) ? "host.containers.internal" : "host.docker.internal";
|
||||||
|
}
|
||||||
|
}
|
205
playwright/plugins/synapse/index.ts
Normal file
205
playwright/plugins/synapse/index.ts
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as path from "path";
|
||||||
|
import * as os from "os";
|
||||||
|
import * as crypto from "crypto";
|
||||||
|
import * as fse from "fs-extra";
|
||||||
|
import { APIRequestContext } from "@playwright/test";
|
||||||
|
|
||||||
|
import { getFreePort } from "../utils/port";
|
||||||
|
import { Docker } from "../docker";
|
||||||
|
import {
|
||||||
|
HomeserverConfig,
|
||||||
|
HomeserverInstance,
|
||||||
|
Homeserver,
|
||||||
|
StartHomeserverOpts,
|
||||||
|
Credentials,
|
||||||
|
} from "../utils/homeserver";
|
||||||
|
|
||||||
|
function randB64Bytes(numBytes: number): string {
|
||||||
|
return crypto.randomBytes(numBytes).toString("base64").replace(/=*$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cfgDirFromTemplate(
|
||||||
|
opts: StartHomeserverOpts,
|
||||||
|
): Promise<HomeserverConfig & { registrationSecret: string }> {
|
||||||
|
const templateDir = path.join(__dirname, "templates", opts.template);
|
||||||
|
|
||||||
|
const stats = await fse.stat(templateDir);
|
||||||
|
if (!stats?.isDirectory) {
|
||||||
|
throw new Error(`No such template: ${opts.template}`);
|
||||||
|
}
|
||||||
|
const tempDir = await fse.mkdtemp(path.join(os.tmpdir(), "react-sdk-synapsedocker-"));
|
||||||
|
|
||||||
|
// copy the contents of the template dir, omitting homeserver.yaml as we'll template that
|
||||||
|
console.log(`Copy ${templateDir} -> ${tempDir}`);
|
||||||
|
await fse.copy(templateDir, tempDir, { filter: (f) => path.basename(f) !== "homeserver.yaml" });
|
||||||
|
|
||||||
|
const registrationSecret = randB64Bytes(16);
|
||||||
|
const macaroonSecret = randB64Bytes(16);
|
||||||
|
const formSecret = randB64Bytes(16);
|
||||||
|
|
||||||
|
const port = await getFreePort();
|
||||||
|
const baseUrl = `http://localhost:${port}`;
|
||||||
|
|
||||||
|
// now copy homeserver.yaml, applying substitutions
|
||||||
|
const templateHomeserver = path.join(templateDir, "homeserver.yaml");
|
||||||
|
const outputHomeserver = path.join(tempDir, "homeserver.yaml");
|
||||||
|
console.log(`Gen ${templateHomeserver} -> ${outputHomeserver}`);
|
||||||
|
let hsYaml = await fse.readFile(templateHomeserver, "utf8");
|
||||||
|
hsYaml = hsYaml.replace(/{{REGISTRATION_SECRET}}/g, registrationSecret);
|
||||||
|
hsYaml = hsYaml.replace(/{{MACAROON_SECRET_KEY}}/g, macaroonSecret);
|
||||||
|
hsYaml = hsYaml.replace(/{{FORM_SECRET}}/g, formSecret);
|
||||||
|
hsYaml = hsYaml.replace(/{{PUBLIC_BASEURL}}/g, baseUrl);
|
||||||
|
if (opts.oAuthServerPort) {
|
||||||
|
hsYaml = hsYaml.replace(/{{OAUTH_SERVER_PORT}}/g, opts.oAuthServerPort.toString());
|
||||||
|
}
|
||||||
|
hsYaml = hsYaml.replace(/{{HOST_DOCKER_INTERNAL}}/g, await Docker.hostnameOfHost());
|
||||||
|
if (opts.variables) {
|
||||||
|
let fetchedHostContainer: Awaited<ReturnType<typeof Docker.hostnameOfHost>> | null = null;
|
||||||
|
for (const key in opts.variables) {
|
||||||
|
let value = String(opts.variables[key]);
|
||||||
|
|
||||||
|
if (value === "{{HOST_DOCKER_INTERNAL}}") {
|
||||||
|
if (!fetchedHostContainer) {
|
||||||
|
fetchedHostContainer = await Docker.hostnameOfHost();
|
||||||
|
}
|
||||||
|
value = fetchedHostContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
hsYaml = hsYaml.replace(new RegExp("%" + key + "%", "g"), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await fse.writeFile(outputHomeserver, hsYaml);
|
||||||
|
|
||||||
|
// now generate a signing key (we could use synapse's config generation for
|
||||||
|
// this, or we could just do this...)
|
||||||
|
// NB. This assumes the homeserver.yaml specifies the key in this location
|
||||||
|
const signingKey = randB64Bytes(32);
|
||||||
|
const outputSigningKey = path.join(tempDir, "localhost.signing.key");
|
||||||
|
console.log(`Gen -> ${outputSigningKey}`);
|
||||||
|
await fse.writeFile(outputSigningKey, `ed25519 x ${signingKey}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
port,
|
||||||
|
baseUrl,
|
||||||
|
configDir: tempDir,
|
||||||
|
registrationSecret,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Synapse implements Homeserver, HomeserverInstance {
|
||||||
|
private docker: Docker = new Docker();
|
||||||
|
public config: HomeserverConfig & { serverId: string; registrationSecret: string };
|
||||||
|
|
||||||
|
public constructor(private readonly request: APIRequestContext) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a synapse instance: the template must be the name of
|
||||||
|
* one of the templates in the playwright/plugins/synapsedocker/templates
|
||||||
|
* directory.
|
||||||
|
*
|
||||||
|
* Any value in opts.variables that is set to `{{HOST_DOCKER_INTERNAL}}'
|
||||||
|
* will be replaced with 'host.docker.internal' (if we are on Docker) or
|
||||||
|
* 'host.containers.internal' if we are on Podman.
|
||||||
|
*/
|
||||||
|
public async start(opts: StartHomeserverOpts): Promise<HomeserverInstance> {
|
||||||
|
if (this.config) await this.stop();
|
||||||
|
|
||||||
|
const synCfg = await cfgDirFromTemplate(opts);
|
||||||
|
console.log(`Starting synapse with config dir ${synCfg.configDir}...`);
|
||||||
|
const dockerSynapseParams = ["--rm", "-v", `${synCfg.configDir}:/data`, "-p", `${synCfg.port}:8008/tcp`];
|
||||||
|
if (await Docker.isPodman()) {
|
||||||
|
// Make host.containers.internal work to allow Synapse to talk to the test OIDC server.
|
||||||
|
dockerSynapseParams.push("--network");
|
||||||
|
dockerSynapseParams.push("slirp4netns:allow_host_loopback=true");
|
||||||
|
} else {
|
||||||
|
// Make host.docker.internal work to allow Synapse to talk to the test OIDC server.
|
||||||
|
dockerSynapseParams.push("--add-host");
|
||||||
|
dockerSynapseParams.push("host.docker.internal:host-gateway");
|
||||||
|
}
|
||||||
|
const synapseId = await this.docker.run({
|
||||||
|
image: "matrixdotorg/synapse:develop",
|
||||||
|
containerName: `react-sdk-playwright-synapse`,
|
||||||
|
params: dockerSynapseParams,
|
||||||
|
cmd: ["run"],
|
||||||
|
});
|
||||||
|
console.log(`Started synapse with id ${synapseId} on port ${synCfg.port}.`);
|
||||||
|
// Await Synapse healthcheck
|
||||||
|
await this.docker.exec([
|
||||||
|
"curl",
|
||||||
|
"--connect-timeout",
|
||||||
|
"30",
|
||||||
|
"--retry",
|
||||||
|
"30",
|
||||||
|
"--retry-delay",
|
||||||
|
"1",
|
||||||
|
"--retry-all-errors",
|
||||||
|
"--silent",
|
||||||
|
"http://localhost:8008/health",
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.config = {
|
||||||
|
...synCfg,
|
||||||
|
serverId: synapseId,
|
||||||
|
};
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop(): Promise<void> {
|
||||||
|
if (!this.config) throw new Error("Missing existing synapse instance, did you call stop() before start()?");
|
||||||
|
const id = this.config.serverId;
|
||||||
|
const synapseLogsPath = path.join("playwright", "synapselogs", id);
|
||||||
|
await fse.ensureDir(synapseLogsPath);
|
||||||
|
await this.docker.persistLogsToFile({
|
||||||
|
stdoutFile: path.join(synapseLogsPath, "stdout.log"),
|
||||||
|
stderrFile: path.join(synapseLogsPath, "stderr.log"),
|
||||||
|
});
|
||||||
|
await this.docker.stop();
|
||||||
|
await fse.remove(this.config.configDir);
|
||||||
|
console.log(`Stopped synapse id ${id}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async registerUser(username: string, password: string, displayName?: string): Promise<Credentials> {
|
||||||
|
const url = `${this.config.baseUrl}/_synapse/admin/v1/register`;
|
||||||
|
const { nonce } = await this.request.get(url).then((r) => r.json());
|
||||||
|
const mac = crypto
|
||||||
|
.createHmac("sha1", this.config.registrationSecret)
|
||||||
|
.update(`${nonce}\0${username}\0${password}\0notadmin`)
|
||||||
|
.digest("hex");
|
||||||
|
const res = await this.request.post(url, {
|
||||||
|
data: {
|
||||||
|
nonce,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
mac,
|
||||||
|
admin: false,
|
||||||
|
displayname: displayName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
return {
|
||||||
|
homeServer: data.home_server,
|
||||||
|
accessToken: data.access_token,
|
||||||
|
userId: data.user_id,
|
||||||
|
deviceId: data.device_id,
|
||||||
|
password,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
3
playwright/plugins/synapse/templates/COPYME/README.md
Normal file
3
playwright/plugins/synapse/templates/COPYME/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Meta-template for synapse templates
|
||||||
|
|
||||||
|
To make another template, you can copy this directory
|
72
playwright/plugins/synapse/templates/COPYME/homeserver.yaml
Normal file
72
playwright/plugins/synapse/templates/COPYME/homeserver.yaml
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
server_name: "localhost"
|
||||||
|
pid_file: /data/homeserver.pid
|
||||||
|
# XXX: This won't actually be right: it lets docker allocate an ephemeral port,
|
||||||
|
# so we have a chicken-and-egg problem
|
||||||
|
public_baseurl: http://localhost:8008/
|
||||||
|
# Listener is always port 8008 (configured in the container)
|
||||||
|
listeners:
|
||||||
|
- port: 8008
|
||||||
|
tls: false
|
||||||
|
bind_addresses: ["::"]
|
||||||
|
type: http
|
||||||
|
x_forwarded: true
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- names: [client, federation, consent]
|
||||||
|
compress: false
|
||||||
|
|
||||||
|
# An sqlite in-memory database is fast & automatically wipes each time
|
||||||
|
database:
|
||||||
|
name: "sqlite3"
|
||||||
|
args:
|
||||||
|
database: ":memory:"
|
||||||
|
|
||||||
|
# Needs to be configured to log to the console like a good docker process
|
||||||
|
log_config: "/data/log.config"
|
||||||
|
|
||||||
|
rc_messages_per_second: 10000
|
||||||
|
rc_message_burst_count: 10000
|
||||||
|
rc_registration:
|
||||||
|
per_second: 10000
|
||||||
|
burst_count: 10000
|
||||||
|
|
||||||
|
rc_login:
|
||||||
|
address:
|
||||||
|
per_second: 10000
|
||||||
|
burst_count: 10000
|
||||||
|
account:
|
||||||
|
per_second: 10000
|
||||||
|
burst_count: 10000
|
||||||
|
failed_attempts:
|
||||||
|
per_second: 10000
|
||||||
|
burst_count: 10000
|
||||||
|
|
||||||
|
media_store_path: "/data/media_store"
|
||||||
|
uploads_path: "/data/uploads"
|
||||||
|
enable_registration: true
|
||||||
|
enable_registration_without_verification: true
|
||||||
|
disable_msisdn_registration: false
|
||||||
|
# These placeholders will be be replaced with values generated at start
|
||||||
|
registration_shared_secret: "{{REGISTRATION_SECRET}}"
|
||||||
|
report_stats: false
|
||||||
|
macaroon_secret_key: "{{MACAROON_SECRET_KEY}}"
|
||||||
|
form_secret: "{{FORM_SECRET}}"
|
||||||
|
# Signing key must be here: it will be generated to this file
|
||||||
|
signing_key_path: "/data/localhost.signing.key"
|
||||||
|
email:
|
||||||
|
enable_notifs: false
|
||||||
|
smtp_host: "localhost"
|
||||||
|
smtp_port: 25
|
||||||
|
smtp_user: "exampleusername"
|
||||||
|
smtp_pass: "examplepassword"
|
||||||
|
require_transport_security: False
|
||||||
|
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>"
|
||||||
|
app_name: Matrix
|
||||||
|
notif_template_html: notif_mail.html
|
||||||
|
notif_template_text: notif_mail.txt
|
||||||
|
notif_for_new_users: True
|
||||||
|
client_base_url: "http://localhost/element"
|
||||||
|
|
||||||
|
trusted_key_servers:
|
||||||
|
- server_name: "matrix.org"
|
||||||
|
suppress_key_server_warning: true
|
50
playwright/plugins/synapse/templates/COPYME/log.config
Normal file
50
playwright/plugins/synapse/templates/COPYME/log.config
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# Log configuration for Synapse.
|
||||||
|
#
|
||||||
|
# This is a YAML file containing a standard Python logging configuration
|
||||||
|
# dictionary. See [1] for details on the valid settings.
|
||||||
|
#
|
||||||
|
# Synapse also supports structured logging for machine readable logs which can
|
||||||
|
# be ingested by ELK stacks. See [2] for details.
|
||||||
|
#
|
||||||
|
# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
|
||||||
|
# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html
|
||||||
|
|
||||||
|
version: 1
|
||||||
|
|
||||||
|
formatters:
|
||||||
|
precise:
|
||||||
|
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
# A handler that writes logs to stderr. Unused by default, but can be used
|
||||||
|
# instead of "buffer" and "file" in the logger handlers.
|
||||||
|
console:
|
||||||
|
class: logging.StreamHandler
|
||||||
|
formatter: precise
|
||||||
|
|
||||||
|
loggers:
|
||||||
|
synapse.storage.SQL:
|
||||||
|
# beware: increasing this to DEBUG will make synapse log sensitive
|
||||||
|
# information such as access tokens.
|
||||||
|
level: INFO
|
||||||
|
|
||||||
|
twisted:
|
||||||
|
# We send the twisted logging directly to the file handler,
|
||||||
|
# to work around https://github.com/matrix-org/synapse/issues/3471
|
||||||
|
# when using "buffer" logger. Use "console" to log to stderr instead.
|
||||||
|
handlers: [console]
|
||||||
|
propagate: false
|
||||||
|
|
||||||
|
root:
|
||||||
|
level: INFO
|
||||||
|
|
||||||
|
# Write logs to the `buffer` handler, which will buffer them together in memory,
|
||||||
|
# then write them to a file.
|
||||||
|
#
|
||||||
|
# Replace "buffer" with "console" to log to stderr instead. (Note that you'll
|
||||||
|
# also need to update the configuration for the `twisted` logger above, in
|
||||||
|
# this case.)
|
||||||
|
#
|
||||||
|
handlers: [console]
|
||||||
|
|
||||||
|
disable_existing_loggers: false
|
1
playwright/plugins/synapse/templates/consent/README.md
Normal file
1
playwright/plugins/synapse/templates/consent/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
A synapse configured with user privacy consent enabled
|
84
playwright/plugins/synapse/templates/consent/homeserver.yaml
Normal file
84
playwright/plugins/synapse/templates/consent/homeserver.yaml
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
server_name: "localhost"
|
||||||
|
pid_file: /data/homeserver.pid
|
||||||
|
public_baseurl: "{{PUBLIC_BASEURL}}"
|
||||||
|
listeners:
|
||||||
|
- port: 8008
|
||||||
|
tls: false
|
||||||
|
bind_addresses: ["::"]
|
||||||
|
type: http
|
||||||
|
x_forwarded: true
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- names: [client, federation, consent]
|
||||||
|
compress: false
|
||||||
|
|
||||||
|
database:
|
||||||
|
name: "sqlite3"
|
||||||
|
args:
|
||||||
|
database: ":memory:"
|
||||||
|
|
||||||
|
log_config: "/data/log.config"
|
||||||
|
|
||||||
|
rc_messages_per_second: 10000
|
||||||
|
rc_message_burst_count: 10000
|
||||||
|
rc_registration:
|
||||||
|
per_second: 10000
|
||||||
|
burst_count: 10000
|
||||||
|
|
||||||
|
rc_login:
|
||||||
|
address:
|
||||||
|
per_second: 10000
|
||||||
|
burst_count: 10000
|
||||||
|
account:
|
||||||
|
per_second: 10000
|
||||||
|
burst_count: 10000
|
||||||
|
failed_attempts:
|
||||||
|
per_second: 10000
|
||||||
|
burst_count: 10000
|
||||||
|
|
||||||
|
media_store_path: "/data/media_store"
|
||||||
|
uploads_path: "/data/uploads"
|
||||||
|
enable_registration: true
|
||||||
|
enable_registration_without_verification: true
|
||||||
|
disable_msisdn_registration: false
|
||||||
|
registration_shared_secret: "{{REGISTRATION_SECRET}}"
|
||||||
|
report_stats: false
|
||||||
|
macaroon_secret_key: "{{MACAROON_SECRET_KEY}}"
|
||||||
|
form_secret: "{{FORM_SECRET}}"
|
||||||
|
signing_key_path: "/data/localhost.signing.key"
|
||||||
|
email:
|
||||||
|
enable_notifs: false
|
||||||
|
smtp_host: "localhost"
|
||||||
|
smtp_port: 25
|
||||||
|
smtp_user: "exampleusername"
|
||||||
|
smtp_pass: "examplepassword"
|
||||||
|
require_transport_security: False
|
||||||
|
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>"
|
||||||
|
app_name: Matrix
|
||||||
|
notif_template_html: notif_mail.html
|
||||||
|
notif_template_text: notif_mail.txt
|
||||||
|
notif_for_new_users: True
|
||||||
|
client_base_url: "http://localhost/element"
|
||||||
|
|
||||||
|
user_consent:
|
||||||
|
template_dir: /data/res/templates/privacy
|
||||||
|
version: 1.0
|
||||||
|
server_notice_content:
|
||||||
|
msgtype: m.text
|
||||||
|
body: >-
|
||||||
|
To continue using this homeserver you must review and agree to the
|
||||||
|
terms and conditions at %(consent_uri)s
|
||||||
|
send_server_notice_to_guests: True
|
||||||
|
block_events_error: >-
|
||||||
|
To continue using this homeserver you must review and agree to the
|
||||||
|
terms and conditions at %(consent_uri)s
|
||||||
|
require_at_registration: true
|
||||||
|
|
||||||
|
server_notices:
|
||||||
|
system_mxid_localpart: notices
|
||||||
|
system_mxid_display_name: "Server Notices"
|
||||||
|
system_mxid_avatar_url: "mxc://localhost:5005/oumMVlgDnLYFaPVkExemNVVZ"
|
||||||
|
room_name: "Server Notices"
|
||||||
|
trusted_key_servers:
|
||||||
|
- server_name: "matrix.org"
|
||||||
|
suppress_key_server_warning: true
|
50
playwright/plugins/synapse/templates/consent/log.config
Normal file
50
playwright/plugins/synapse/templates/consent/log.config
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# Log configuration for Synapse.
|
||||||
|
#
|
||||||
|
# This is a YAML file containing a standard Python logging configuration
|
||||||
|
# dictionary. See [1] for details on the valid settings.
|
||||||
|
#
|
||||||
|
# Synapse also supports structured logging for machine readable logs which can
|
||||||
|
# be ingested by ELK stacks. See [2] for details.
|
||||||
|
#
|
||||||
|
# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
|
||||||
|
# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html
|
||||||
|
|
||||||
|
version: 1
|
||||||
|
|
||||||
|
formatters:
|
||||||
|
precise:
|
||||||
|
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
# A handler that writes logs to stderr. Unused by default, but can be used
|
||||||
|
# instead of "buffer" and "file" in the logger handlers.
|
||||||
|
console:
|
||||||
|
class: logging.StreamHandler
|
||||||
|
formatter: precise
|
||||||
|
|
||||||
|
loggers:
|
||||||
|
synapse.storage.SQL:
|
||||||
|
# beware: increasing this to DEBUG will make synapse log sensitive
|
||||||
|
# information such as access tokens.
|
||||||
|
level: DEBUG
|
||||||
|
|
||||||
|
twisted:
|
||||||
|
# We send the twisted logging directly to the file handler,
|
||||||
|
# to work around https://github.com/matrix-org/synapse/issues/3471
|
||||||
|
# when using "buffer" logger. Use "console" to log to stderr instead.
|
||||||
|
handlers: [console]
|
||||||
|
propagate: false
|
||||||
|
|
||||||
|
root:
|
||||||
|
level: DEBUG
|
||||||
|
|
||||||
|
# Write logs to the `buffer` handler, which will buffer them together in memory,
|
||||||
|
# then write them to a file.
|
||||||
|
#
|
||||||
|
# Replace "buffer" with "console" to log to stderr instead. (Note that you'll
|
||||||
|
# also need to update the configuration for the `twisted` logger above, in
|
||||||
|
# this case.)
|
||||||
|
#
|
||||||
|
handlers: [console]
|
||||||
|
|
||||||
|
disable_existing_loggers: false
|
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Test Privacy policy</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% if has_consented %}
|
||||||
|
<p>Thank you, you've already accepted the license.</p>
|
||||||
|
{% else %}
|
||||||
|
<p>Please accept the license!</p>
|
||||||
|
<form method="post" action="consent">
|
||||||
|
<input type="hidden" name="v" value="{{version}}" />
|
||||||
|
<input type="hidden" name="u" value="{{user}}" />
|
||||||
|
<input type="hidden" name="h" value="{{userhmac}}" />
|
||||||
|
<input type="submit" value="Sure thing!" />
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Test Privacy policy</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Danke schoen</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
1
playwright/plugins/synapse/templates/default/README.md
Normal file
1
playwright/plugins/synapse/templates/default/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
A synapse configured with user privacy consent disabled
|
94
playwright/plugins/synapse/templates/default/homeserver.yaml
Normal file
94
playwright/plugins/synapse/templates/default/homeserver.yaml
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
server_name: "localhost"
|
||||||
|
pid_file: /data/homeserver.pid
|
||||||
|
public_baseurl: "{{PUBLIC_BASEURL}}"
|
||||||
|
listeners:
|
||||||
|
- port: 8008
|
||||||
|
tls: false
|
||||||
|
bind_addresses: ["::"]
|
||||||
|
type: http
|
||||||
|
x_forwarded: true
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- names: [client]
|
||||||
|
compress: false
|
||||||
|
|
||||||
|
database:
|
||||||
|
name: "sqlite3"
|
||||||
|
args:
|
||||||
|
database: ":memory:"
|
||||||
|
|
||||||
|
log_config: "/data/log.config"
|
||||||
|
|
||||||
|
rc_messages_per_second: 10000
|
||||||
|
rc_message_burst_count: 10000
|
||||||
|
rc_registration:
|
||||||
|
per_second: 10000
|
||||||
|
burst_count: 10000
|
||||||
|
rc_joins:
|
||||||
|
local:
|
||||||
|
per_second: 9999
|
||||||
|
burst_count: 9999
|
||||||
|
remote:
|
||||||
|
per_second: 9999
|
||||||
|
burst_count: 9999
|
||||||
|
rc_joins_per_room:
|
||||||
|
per_second: 9999
|
||||||
|
burst_count: 9999
|
||||||
|
rc_3pid_validation:
|
||||||
|
per_second: 1000
|
||||||
|
burst_count: 1000
|
||||||
|
|
||||||
|
rc_invites:
|
||||||
|
per_room:
|
||||||
|
per_second: 1000
|
||||||
|
burst_count: 1000
|
||||||
|
per_user:
|
||||||
|
per_second: 1000
|
||||||
|
burst_count: 1000
|
||||||
|
|
||||||
|
rc_login:
|
||||||
|
address:
|
||||||
|
per_second: 10000
|
||||||
|
burst_count: 10000
|
||||||
|
account:
|
||||||
|
per_second: 10000
|
||||||
|
burst_count: 10000
|
||||||
|
failed_attempts:
|
||||||
|
per_second: 10000
|
||||||
|
burst_count: 10000
|
||||||
|
|
||||||
|
media_store_path: "/data/media_store"
|
||||||
|
uploads_path: "/data/uploads"
|
||||||
|
enable_registration: true
|
||||||
|
enable_registration_without_verification: true
|
||||||
|
disable_msisdn_registration: false
|
||||||
|
registration_shared_secret: "{{REGISTRATION_SECRET}}"
|
||||||
|
report_stats: false
|
||||||
|
macaroon_secret_key: "{{MACAROON_SECRET_KEY}}"
|
||||||
|
form_secret: "{{FORM_SECRET}}"
|
||||||
|
signing_key_path: "/data/localhost.signing.key"
|
||||||
|
|
||||||
|
trusted_key_servers:
|
||||||
|
- server_name: "matrix.org"
|
||||||
|
suppress_key_server_warning: true
|
||||||
|
|
||||||
|
ui_auth:
|
||||||
|
session_timeout: "300s"
|
||||||
|
|
||||||
|
oidc_providers:
|
||||||
|
- idp_id: test
|
||||||
|
idp_name: "OAuth test"
|
||||||
|
issuer: "http://localhost:{{OAUTH_SERVER_PORT}}/oauth"
|
||||||
|
authorization_endpoint: "http://localhost:{{OAUTH_SERVER_PORT}}/oauth/auth.html"
|
||||||
|
# the token endpoint receives requests from synapse, rather than the webapp, so needs to escape the docker container.
|
||||||
|
# Hence, HOST_DOCKER_INTERNAL rather than localhost. This is set to
|
||||||
|
# host.docker.internal on Docker and host.containers.internal on Podman.
|
||||||
|
token_endpoint: "http://{{HOST_DOCKER_INTERNAL}}:{{OAUTH_SERVER_PORT}}/oauth/token"
|
||||||
|
userinfo_endpoint: "http://{{HOST_DOCKER_INTERNAL}}:{{OAUTH_SERVER_PORT}}/oauth/userinfo"
|
||||||
|
client_id: "synapse"
|
||||||
|
discover: false
|
||||||
|
scopes: ["profile"]
|
||||||
|
skip_verification: true
|
||||||
|
user_mapping_provider:
|
||||||
|
config:
|
||||||
|
display_name_template: "{{ user.name }}"
|
50
playwright/plugins/synapse/templates/default/log.config
Normal file
50
playwright/plugins/synapse/templates/default/log.config
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# Log configuration for Synapse.
|
||||||
|
#
|
||||||
|
# This is a YAML file containing a standard Python logging configuration
|
||||||
|
# dictionary. See [1] for details on the valid settings.
|
||||||
|
#
|
||||||
|
# Synapse also supports structured logging for machine readable logs which can
|
||||||
|
# be ingested by ELK stacks. See [2] for details.
|
||||||
|
#
|
||||||
|
# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
|
||||||
|
# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html
|
||||||
|
|
||||||
|
version: 1
|
||||||
|
|
||||||
|
formatters:
|
||||||
|
precise:
|
||||||
|
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
# A handler that writes logs to stderr. Unused by default, but can be used
|
||||||
|
# instead of "buffer" and "file" in the logger handlers.
|
||||||
|
console:
|
||||||
|
class: logging.StreamHandler
|
||||||
|
formatter: precise
|
||||||
|
|
||||||
|
loggers:
|
||||||
|
synapse.storage.SQL:
|
||||||
|
# beware: increasing this to DEBUG will make synapse log sensitive
|
||||||
|
# information such as access tokens.
|
||||||
|
level: DEBUG
|
||||||
|
|
||||||
|
twisted:
|
||||||
|
# We send the twisted logging directly to the file handler,
|
||||||
|
# to work around https://github.com/matrix-org/synapse/issues/3471
|
||||||
|
# when using "buffer" logger. Use "console" to log to stderr instead.
|
||||||
|
handlers: [console]
|
||||||
|
propagate: false
|
||||||
|
|
||||||
|
root:
|
||||||
|
level: DEBUG
|
||||||
|
|
||||||
|
# Write logs to the `buffer` handler, which will buffer them together in memory,
|
||||||
|
# then write them to a file.
|
||||||
|
#
|
||||||
|
# Replace "buffer" with "console" to log to stderr instead. (Note that you'll
|
||||||
|
# also need to update the configuration for the `twisted` logger above, in
|
||||||
|
# this case.)
|
||||||
|
#
|
||||||
|
handlers: [console]
|
||||||
|
|
||||||
|
disable_existing_loggers: false
|
1
playwright/plugins/synapse/templates/email/README.md
Normal file
1
playwright/plugins/synapse/templates/email/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
A synapse configured to require an email for registration
|
44
playwright/plugins/synapse/templates/email/homeserver.yaml
Normal file
44
playwright/plugins/synapse/templates/email/homeserver.yaml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
server_name: "localhost"
|
||||||
|
pid_file: /data/homeserver.pid
|
||||||
|
public_baseurl: "{{PUBLIC_BASEURL}}"
|
||||||
|
listeners:
|
||||||
|
- port: 8008
|
||||||
|
tls: false
|
||||||
|
bind_addresses: ["::"]
|
||||||
|
type: http
|
||||||
|
x_forwarded: true
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- names: [client]
|
||||||
|
compress: false
|
||||||
|
|
||||||
|
database:
|
||||||
|
name: "sqlite3"
|
||||||
|
args:
|
||||||
|
database: ":memory:"
|
||||||
|
|
||||||
|
log_config: "/data/log.config"
|
||||||
|
|
||||||
|
media_store_path: "/data/media_store"
|
||||||
|
uploads_path: "/data/uploads"
|
||||||
|
enable_registration: true
|
||||||
|
registrations_require_3pid:
|
||||||
|
- email
|
||||||
|
registration_shared_secret: "{{REGISTRATION_SECRET}}"
|
||||||
|
report_stats: false
|
||||||
|
macaroon_secret_key: "{{MACAROON_SECRET_KEY}}"
|
||||||
|
form_secret: "{{FORM_SECRET}}"
|
||||||
|
signing_key_path: "/data/localhost.signing.key"
|
||||||
|
|
||||||
|
trusted_key_servers:
|
||||||
|
- server_name: "matrix.org"
|
||||||
|
suppress_key_server_warning: true
|
||||||
|
|
||||||
|
ui_auth:
|
||||||
|
session_timeout: "300s"
|
||||||
|
|
||||||
|
email:
|
||||||
|
smtp_host: "%SMTP_HOST%"
|
||||||
|
smtp_port: %SMTP_PORT%
|
||||||
|
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>"
|
||||||
|
app_name: my_branded_matrix_server
|
50
playwright/plugins/synapse/templates/email/log.config
Normal file
50
playwright/plugins/synapse/templates/email/log.config
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# Log configuration for Synapse.
|
||||||
|
#
|
||||||
|
# This is a YAML file containing a standard Python logging configuration
|
||||||
|
# dictionary. See [1] for details on the valid settings.
|
||||||
|
#
|
||||||
|
# Synapse also supports structured logging for machine readable logs which can
|
||||||
|
# be ingested by ELK stacks. See [2] for details.
|
||||||
|
#
|
||||||
|
# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
|
||||||
|
# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html
|
||||||
|
|
||||||
|
version: 1
|
||||||
|
|
||||||
|
formatters:
|
||||||
|
precise:
|
||||||
|
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
# A handler that writes logs to stderr. Unused by default, but can be used
|
||||||
|
# instead of "buffer" and "file" in the logger handlers.
|
||||||
|
console:
|
||||||
|
class: logging.StreamHandler
|
||||||
|
formatter: precise
|
||||||
|
|
||||||
|
loggers:
|
||||||
|
synapse.storage.SQL:
|
||||||
|
# beware: increasing this to DEBUG will make synapse log sensitive
|
||||||
|
# information such as access tokens.
|
||||||
|
level: INFO
|
||||||
|
|
||||||
|
twisted:
|
||||||
|
# We send the twisted logging directly to the file handler,
|
||||||
|
# to work around https://github.com/matrix-org/synapse/issues/3471
|
||||||
|
# when using "buffer" logger. Use "console" to log to stderr instead.
|
||||||
|
handlers: [console]
|
||||||
|
propagate: false
|
||||||
|
|
||||||
|
root:
|
||||||
|
level: INFO
|
||||||
|
|
||||||
|
# Write logs to the `buffer` handler, which will buffer them together in memory,
|
||||||
|
# then write them to a file.
|
||||||
|
#
|
||||||
|
# Replace "buffer" with "console" to log to stderr instead. (Note that you'll
|
||||||
|
# also need to update the configuration for the `twisted` logger above, in
|
||||||
|
# this case.)
|
||||||
|
#
|
||||||
|
handlers: [console]
|
||||||
|
|
||||||
|
disable_existing_loggers: false
|
57
playwright/plugins/utils/homeserver.ts
Normal file
57
playwright/plugins/utils/homeserver.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface HomeserverConfig {
|
||||||
|
readonly configDir: string;
|
||||||
|
readonly baseUrl: string;
|
||||||
|
readonly port: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HomeserverInstance {
|
||||||
|
readonly config: HomeserverConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a user on the given Homeserver using the shared registration secret.
|
||||||
|
* @param username the username of the user to register
|
||||||
|
* @param password the password of the user to register
|
||||||
|
* @param displayName optional display name to set on the newly registered user
|
||||||
|
*/
|
||||||
|
registerUser(username: string, password: string, displayName?: string): Promise<Credentials>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StartHomeserverOpts {
|
||||||
|
/** path to template within playwright/plugins/{homeserver}docker/template/ directory. */
|
||||||
|
template: string;
|
||||||
|
|
||||||
|
/** Port of an OAuth server to configure the homeserver to use */
|
||||||
|
oAuthServerPort?: number;
|
||||||
|
|
||||||
|
/** Additional variables to inject into the configuration template **/
|
||||||
|
variables?: Record<string, string | number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Homeserver {
|
||||||
|
start(opts: StartHomeserverOpts): Promise<HomeserverInstance>;
|
||||||
|
stop(): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Credentials {
|
||||||
|
accessToken: string;
|
||||||
|
userId: string;
|
||||||
|
deviceId: string;
|
||||||
|
homeServer: string;
|
||||||
|
password: string;
|
||||||
|
}
|
27
playwright/plugins/utils/port.ts
Normal file
27
playwright/plugins/utils/port.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as net from "net";
|
||||||
|
|
||||||
|
export async function getFreePort(): Promise<number> {
|
||||||
|
return new Promise<number>((resolve) => {
|
||||||
|
const srv = net.createServer();
|
||||||
|
srv.listen(0, () => {
|
||||||
|
const port = (<net.AddressInfo>srv.address()).port;
|
||||||
|
srv.close(() => resolve(port));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
24
yarn.lock
24
yarn.lock
|
@ -3434,7 +3434,7 @@ aws4@^1.8.0:
|
||||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3"
|
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3"
|
||||||
integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==
|
integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==
|
||||||
|
|
||||||
axe-core@4.8.2:
|
axe-core@4.8.2, axe-core@^4.5.1:
|
||||||
version "4.8.2"
|
version "4.8.2"
|
||||||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.8.2.tgz#2f6f3cde40935825cf4465e3c1c9e77b240ff6ae"
|
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.8.2.tgz#2f6f3cde40935825cf4465e3c1c9e77b240ff6ae"
|
||||||
integrity sha512-/dlp0fxyM3R8YW7MFzaHWXrf4zzbr0vaYb23VBFCl83R7nWNPg/yaQw2Dc8jzCMmDVLhSdzH8MjrsuIUuvX+6g==
|
integrity sha512-/dlp0fxyM3R8YW7MFzaHWXrf4zzbr0vaYb23VBFCl83R7nWNPg/yaQw2Dc8jzCMmDVLhSdzH8MjrsuIUuvX+6g==
|
||||||
|
@ -3444,6 +3444,23 @@ axe-core@=4.7.0:
|
||||||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf"
|
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf"
|
||||||
integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==
|
integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==
|
||||||
|
|
||||||
|
axe-html-reporter@2.2.3:
|
||||||
|
version "2.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/axe-html-reporter/-/axe-html-reporter-2.2.3.tgz#2d56e239fe9bd1f09ba0735d94596bf79dd389a7"
|
||||||
|
integrity sha512-io8aCEt4fJvv43W+33n3zEa8rdplH5Ti2v5fOnth3GBKLhLHarNs7jj46xGfpnGnpaNrz23/tXPHC3HbwTzwwA==
|
||||||
|
dependencies:
|
||||||
|
mustache "^4.0.1"
|
||||||
|
rimraf "^3.0.2"
|
||||||
|
|
||||||
|
axe-playwright@^1.2.3:
|
||||||
|
version "1.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/axe-playwright/-/axe-playwright-1.2.3.tgz#b590b4edf3898ed5784c4932cbad2937115b31f2"
|
||||||
|
integrity sha512-bTxCTNp3kx6sQRMjmuLv8pG3+v+kOCvFXATim1+XUXzW6ykulbbuJdQfgB+vQPNAF9uvYbW2qrv9pg81ZSFV/A==
|
||||||
|
dependencies:
|
||||||
|
axe-core "^4.5.1"
|
||||||
|
axe-html-reporter "2.2.3"
|
||||||
|
picocolors "^1.0.0"
|
||||||
|
|
||||||
axios-retry@^3.7.0:
|
axios-retry@^3.7.0:
|
||||||
version "3.9.1"
|
version "3.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/axios-retry/-/axios-retry-3.9.1.tgz#c8924a8781c8e0a2c5244abf773deb7566b3830d"
|
resolved "https://registry.yarnpkg.com/axios-retry/-/axios-retry-3.9.1.tgz#c8924a8781c8e0a2c5244abf773deb7566b3830d"
|
||||||
|
@ -7748,6 +7765,11 @@ murmurhash-js@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/murmurhash-js/-/murmurhash-js-1.0.0.tgz#b06278e21fc6c37fa5313732b0412bcb6ae15f51"
|
resolved "https://registry.yarnpkg.com/murmurhash-js/-/murmurhash-js-1.0.0.tgz#b06278e21fc6c37fa5313732b0412bcb6ae15f51"
|
||||||
integrity sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==
|
integrity sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==
|
||||||
|
|
||||||
|
mustache@^4.0.1:
|
||||||
|
version "4.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64"
|
||||||
|
integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==
|
||||||
|
|
||||||
nanoid@^3.3.6:
|
nanoid@^3.3.6:
|
||||||
version "3.3.6"
|
version "3.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
|
||||||
|
|
Loading…
Reference in a new issue