Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit f7aab0e

Browse files
authored
Implement dendrite & pinecone support in Playwright (#11943)
Signed-off-by: Michael Telatynski <[email protected]>
1 parent a3cf11a commit f7aab0e

File tree

25 files changed

+589
-27
lines changed

25 files changed

+589
-27
lines changed

playwright/e2e/login/login.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ limitations under the License.
1616

1717
import { test, expect } from "../../element-web-test";
1818
import { doTokenRegistration } from "./utils";
19+
import { isDendrite } from "../../plugins/homeserver/dendrite";
1920

2021
test.describe("Login", () => {
2122
test.describe("m.login.password", () => {
@@ -79,6 +80,8 @@ test.describe("Login", () => {
7980

8081
// tests for old-style SSO login, in which we exchange tokens with Synapse, and Synapse talks to an auth server
8182
test.describe("SSO login", () => {
83+
test.skip(isDendrite, "does not yet support SSO");
84+
8285
test.use({
8386
startHomeserverOpts: ({ oAuthServer }, use) =>
8487
use({

playwright/e2e/login/soft_logout.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import { Page } from "@playwright/test";
1818

1919
import { test, expect } from "../../element-web-test";
2020
import { doTokenRegistration } from "./utils";
21-
import { Credentials } from "../../plugins/utils/homeserver";
21+
import { Credentials } from "../../plugins/homeserver";
22+
import { isDendrite } from "../../plugins/homeserver/dendrite";
2223

2324
test.describe("Soft logout", () => {
2425
test.use({
@@ -54,6 +55,8 @@ test.describe("Soft logout", () => {
5455
});
5556

5657
test.describe("with SSO user", () => {
58+
test.skip(isDendrite, "does not yet support SSO");
59+
5760
test.use({
5861
user: async ({ page, homeserver }, use) => {
5962
const user = await doTokenRegistration(page, homeserver);

playwright/e2e/login/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ limitations under the License.
1616

1717
import { Page, expect } from "@playwright/test";
1818

19-
import { Credentials, HomeserverInstance } from "../../plugins/utils/homeserver";
19+
import { Credentials, HomeserverInstance } from "../../plugins/homeserver";
2020

2121
/** Visit the login page, choose to log in with "OAuth test", register a new account, and redirect back to Element
2222
*/

playwright/e2e/register/email.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@ limitations under the License.
1616

1717
import { test, expect } from "../../element-web-test";
1818
import { MailHogServer } from "../../plugins/mailhog";
19+
import { isDendrite } from "../../plugins/homeserver/dendrite";
1920

2021
test.describe("Email Registration", async () => {
22+
test.skip(isDendrite, "not yet wired up");
23+
2124
test.use({
2225
// eslint-disable-next-line no-empty-pattern
2326
mailhog: async ({}, use) => {

playwright/element-web-test.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ import _ from "lodash";
2020

2121
import type mailhog from "mailhog";
2222
import type { IConfigOptions } from "../src/IConfigOptions";
23-
import { Credentials, HomeserverInstance, StartHomeserverOpts } from "./plugins/utils/homeserver";
24-
import { Synapse } from "./plugins/synapse";
23+
import { Credentials, Homeserver, HomeserverInstance, StartHomeserverOpts } from "./plugins/homeserver";
24+
import { Synapse } from "./plugins/homeserver/synapse";
25+
import { Dendrite, Pinecone } from "./plugins/homeserver/dendrite";
2526
import { Instance } from "./plugins/mailhog";
2627
import { ElementAppPage } from "./pages/ElementAppPage";
2728
import { OAuthServer } from "./plugins/oauth_server";
@@ -89,7 +90,19 @@ export const test = base.extend<
8990
opts = { template: opts };
9091
}
9192

92-
const server = new Synapse(request);
93+
let server: Homeserver;
94+
const homeserverName = process.env["PLAYWRIGHT_HOMESERVER"];
95+
switch (homeserverName) {
96+
case "dendrite":
97+
server = new Dendrite(request);
98+
break;
99+
case "pinecone":
100+
server = new Pinecone(request);
101+
break;
102+
default:
103+
server = new Synapse(request);
104+
}
105+
93106
await use(await server.start(opts));
94107
await server.stop();
95108
},
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
Copyright 2023 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import * as path from "node:path";
18+
import * as os from "node:os";
19+
import * as fse from "fs-extra";
20+
21+
import { getFreePort } from "../../utils/port";
22+
import { Homeserver, HomeserverConfig, HomeserverInstance, StartHomeserverOpts } from "../";
23+
import { randB64Bytes } from "../../utils/rand";
24+
import { Synapse } from "../synapse";
25+
import { Docker } from "../../docker";
26+
27+
const dockerConfigDir = "/etc/dendrite/";
28+
const dendriteConfigFile = "dendrite.yaml";
29+
30+
// Surprisingly, Dendrite implements the same register user Admin API Synapse, so we can just extend it
31+
export class Dendrite extends Synapse implements Homeserver, HomeserverInstance {
32+
public config: HomeserverConfig & { serverId: string };
33+
protected image = "matrixdotorg/dendrite-monolith:main";
34+
protected entrypoint = "/usr/bin/dendrite";
35+
36+
/**
37+
* Start a dendrite instance: the template must be the name of one of the templates
38+
* in the playwright/plugins/dendritedocker/templates directory
39+
* @param opts
40+
*/
41+
public async start(opts: StartHomeserverOpts): Promise<HomeserverInstance> {
42+
const denCfg = await cfgDirFromTemplate(this.image, opts);
43+
44+
console.log(`Starting dendrite with config dir ${denCfg.configDir}...`);
45+
46+
const dendriteId = await this.docker.run({
47+
image: this.image,
48+
params: [
49+
"--rm",
50+
"-v",
51+
`${denCfg.configDir}:` + dockerConfigDir,
52+
"-p",
53+
`${denCfg.port}:8008/tcp`,
54+
"--entrypoint",
55+
this.entrypoint,
56+
],
57+
containerName: `react-sdk-playwright-dendrite`,
58+
cmd: ["--config", dockerConfigDir + dendriteConfigFile, "--really-enable-open-registration", "true", "run"],
59+
});
60+
61+
console.log(`Started dendrite with id ${dendriteId} on port ${denCfg.port}.`);
62+
63+
// Await Dendrite healthcheck
64+
await this.docker.exec([
65+
"curl",
66+
"--connect-timeout",
67+
"30",
68+
"--retry",
69+
"30",
70+
"--retry-delay",
71+
"1",
72+
"--retry-all-errors",
73+
"--silent",
74+
"http://localhost:8008/_matrix/client/versions",
75+
]);
76+
77+
this.config = {
78+
...denCfg,
79+
serverId: dendriteId,
80+
};
81+
return this;
82+
}
83+
84+
public async stop(): Promise<void> {
85+
if (!this.config) throw new Error("Missing existing dendrite instance, did you call stop() before start()?");
86+
87+
const dendriteLogsPath = path.join("playwright", "dendritelogs", this.config.serverId);
88+
await fse.ensureDir(dendriteLogsPath);
89+
90+
await this.docker.persistLogsToFile({
91+
stdoutFile: path.join(dendriteLogsPath, "stdout.log"),
92+
stderrFile: path.join(dendriteLogsPath, "stderr.log"),
93+
});
94+
95+
await this.docker.stop();
96+
97+
await fse.remove(this.config.configDir);
98+
99+
console.log(`Stopped dendrite id ${this.config.serverId}.`);
100+
}
101+
}
102+
103+
export class Pinecone extends Dendrite {
104+
protected image = "matrixdotorg/dendrite-demo-pinecone:main";
105+
protected entrypoint = "/usr/bin/dendrite-demo-pinecone";
106+
}
107+
108+
async function cfgDirFromTemplate(dendriteImage: string, opts: StartHomeserverOpts): Promise<HomeserverConfig> {
109+
const template = "default"; // XXX: for now we only have one template
110+
const templateDir = path.join(__dirname, "templates", template);
111+
112+
const stats = await fse.stat(templateDir);
113+
if (!stats?.isDirectory) {
114+
throw new Error(`No such template: ${template}`);
115+
}
116+
const tempDir = await fse.mkdtemp(path.join(os.tmpdir(), "react-sdk-dendritedocker-"));
117+
118+
// copy the contents of the template dir, omitting homeserver.yaml as we'll template that
119+
console.log(`Copy ${templateDir} -> ${tempDir}`);
120+
await fse.copy(templateDir, tempDir, { filter: (f) => path.basename(f) !== dendriteConfigFile });
121+
122+
const registrationSecret = randB64Bytes(16);
123+
124+
const port = await getFreePort();
125+
const baseUrl = `http://localhost:${port}`;
126+
127+
// now copy homeserver.yaml, applying substitutions
128+
console.log(`Gen ${path.join(templateDir, dendriteConfigFile)}`);
129+
let hsYaml = await fse.readFile(path.join(templateDir, dendriteConfigFile), "utf8");
130+
hsYaml = hsYaml.replace(/{{REGISTRATION_SECRET}}/g, registrationSecret);
131+
await fse.writeFile(path.join(tempDir, dendriteConfigFile), hsYaml);
132+
133+
const docker = new Docker();
134+
await docker.run({
135+
image: dendriteImage,
136+
params: ["--rm", "--entrypoint=", "-v", `${tempDir}:/mnt`],
137+
containerName: `react-sdk-playwright-dendrite-keygen`,
138+
cmd: ["/usr/bin/generate-keys", "-private-key", "/mnt/matrix_key.pem"],
139+
});
140+
141+
return {
142+
port,
143+
baseUrl,
144+
configDir: tempDir,
145+
registrationSecret,
146+
};
147+
}
148+
149+
export function isDendrite(): boolean {
150+
return process.env["PLAYWRIGHT_HOMESERVER"] === "dendrite" || process.env["PLAYWRIGHT_HOMESERVER"] === "pinecone";
151+
}

0 commit comments

Comments
 (0)