Merge pull request #906 from vector-im/playwright

Implement integration tests in Hydrogen using playwright
This commit is contained in:
R Midhun Suresh 2022-10-26 11:28:38 +05:30 committed by GitHub
commit 5f57c2d361
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 2146 additions and 9 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ lib
*.tar.gz
.eslintcache
.tmp
playwright/synapselogs

View File

@ -18,7 +18,8 @@
"start": "vite --port 3000",
"build": "vite build && ./scripts/cleanup.sh",
"build:sdk": "./scripts/sdk/build.sh",
"watch:sdk": "./scripts/sdk/build.sh && yarn run vite build -c vite.sdk-lib-config.js --watch"
"watch:sdk": "./scripts/sdk/build.sh && yarn run vite build -c vite.sdk-lib-config.js --watch",
"test:app": "./scripts/test-app.sh"
},
"repository": {
"type": "git",
@ -31,6 +32,7 @@
},
"homepage": "https://github.com/vector-im/hydrogen-web/#readme",
"devDependencies": {
"@playwright/test": "^1.27.1",
"@typescript-eslint/eslint-plugin": "^4.29.2",
"@typescript-eslint/parser": "^4.29.2",
"acorn": "^8.6.0",

21
playwright.config.ts Normal file
View File

@ -0,0 +1,21 @@
import type { PlaywrightTestConfig } from "@playwright/test";
const BASE_URL = process.env["BASE_URL"] ?? "http://127.0.0.1:3000";
const config: PlaywrightTestConfig = {
use: {
headless: false,
viewport: { width: 1280, height: 720 },
ignoreHTTPSErrors: true,
video: "on-first-retry",
baseURL: BASE_URL,
},
testDir: "./playwright/tests",
globalSetup: require.resolve("./playwright/global-setup"),
webServer: {
command: "yarn start",
url: `${BASE_URL}/#/login`,
},
workers: 1
};
export default config;

View File

@ -0,0 +1,28 @@
/*
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.
*/
const env = {
SYNAPSE_IP_ADDRESS: "172.18.0.5",
SYNAPSE_PORT: "8008",
DEX_IP_ADDRESS: "172.18.0.4",
DEX_PORT: "5556",
}
export default function setupEnvironmentVariables() {
for (const [key, value] of Object.entries(env)) {
process.env[key] = value;
}
}

View File

@ -0,0 +1,108 @@
/*
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.
*/
/// <reference types="cypress" />
import * as path from "path";
import * as os from "os";
import * as fse from "fs-extra";
import {dockerRun, dockerStop } from "../docker";
// A cypress plugins to add command to start & stop dex instances
interface DexConfig {
configDir: string;
baseUrl: string;
port: number;
host: string;
}
export interface DexInstance extends DexConfig {
dexId: string;
}
const dexConfigs = new Map<string, DexInstance>();
async function produceConfigWithSynapseURLAdded(): Promise<DexConfig> {
const templateDir = path.join(__dirname, "template");
const stats = await fse.stat(templateDir);
if (!stats?.isDirectory) {
throw new Error(`Template directory at ${templateDir} not found!`);
}
const tempDir = await fse.mkdtemp(path.join(os.tmpdir(), 'hydrogen-testing-dex-'));
// copy the contents of the template dir, omitting config.yaml as we'll template that
console.log(`Copy ${templateDir} -> ${tempDir}`);
await fse.copy(templateDir, tempDir, { filter: f => path.basename(f) !== 'config.yaml' });
// now copy config.yaml, applying substitutions
console.log(`Gen ${path.join(templateDir, "config.yaml")}`);
let hsYaml = await fse.readFile(path.join(templateDir, "config.yaml"), "utf8");
const synapseHost = process.env.SYNAPSE_IP_ADDRESS;
const synapsePort = process.env.SYNAPSE_PORT;
const synapseAddress = `${synapseHost}:${synapsePort}`;
hsYaml = hsYaml.replace(/{{SYNAPSE_ADDRESS}}/g, synapseAddress);
const dexHost = process.env.DEX_IP_ADDRESS!;
const dexPort = parseInt(process.env.DEX_PORT!, 10);
const dexAddress = `${dexHost}:${dexPort}`;
hsYaml = hsYaml.replace(/{{DEX_ADDRESS}}/g, dexAddress);
await fse.writeFile(path.join(tempDir, "config.yaml"), hsYaml);
const baseUrl = `http://${dexHost}:${dexPort}`;
return {
host: dexHost,
port: dexPort,
baseUrl,
configDir: tempDir,
};
}
export async function dexStart(): Promise<DexInstance> {
const dexCfg = await produceConfigWithSynapseURLAdded();
console.log(`Starting dex with config dir ${dexCfg.configDir}...`);
const dexId = await dockerRun({
image: "bitnami/dex:latest",
containerName: "hydrogen-dex",
dockerParams: [
"--rm",
"-v", `${dexCfg.configDir}:/data`,
`--ip=${dexCfg.host}`,
"-p", `${dexCfg.port}:5556/tcp`,
"--network=hydrogen"
],
applicationParams: [
"serve",
"data/config.yaml",
]
});
console.log(`Started dex with id ${dexId} on port ${dexCfg.port}.`);
const dex: DexInstance = { dexId, ...dexCfg };
dexConfigs.set(dexId, dex);
return dex;
}
export async function dexStop(id: string): Promise<void> {
const dexCfg = dexConfigs.get(id);
if (!dexCfg) throw new Error("Unknown dex ID");
await dockerStop({ containerId: id, });
await fse.remove(dexCfg.configDir);
dexConfigs.delete(id);
console.log(`Stopped dex id ${id}.`);
}

View File

@ -0,0 +1,56 @@
issuer: http://{{DEX_ADDRESS}}/dex
storage:
type: sqlite3
config:
file: data/dev.db
# Configuration for the HTTP endpoints.
web:
http: 0.0.0.0:5556
# Uncomment for HTTPS options.
# https: 127.0.0.1:5554
# tlsCert: /etc/dex/tls.crt
# tlsKey: /etc/dex/tls.key
# Configuration for telemetry
telemetry:
http: 0.0.0.0:5558
# enableProfiling: true
staticClients:
- id: synapse
secret: secret
redirectURIs:
- 'http://{{SYNAPSE_ADDRESS}}/_synapse/client/oidc/callback'
name: 'Synapse'
connectors:
- type: mockCallback
id: mock
name: Example
# - type: google
# id: google
# name: Google
# config:
# issuer: https://accounts.google.com
# # Connector config values starting with a "$" will read from the environment.
# clientID: $GOOGLE_CLIENT_ID
# clientSecret: $GOOGLE_CLIENT_SECRET
# redirectURI: http://127.0.0.1:5556/dex/callback
# hostedDomains:
# - $GOOGLE_HOSTED_DOMAIN
# Let dex keep a list of passwords which can be used to login to dex.
enablePasswordDB: true
# A static list of passwords to login the end user. By identifying here, dex
# won't look in its underlying storage for passwords.
#
# If this option isn't chosen users may be added through the gRPC API.
staticPasswords:
- email: "admin@example.com"
# bcrypt hash of the string "password": $(echo password | htpasswd -BinC 10 admin | cut -d: -f2)
hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
username: "admin"
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"

Binary file not shown.

View File

@ -0,0 +1,151 @@
/*
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.
*/
/// <reference types="cypress" />
import * as os from "os";
import * as childProcess from "child_process";
import * as fse from "fs-extra";
export function dockerRun(args: {
image: string;
containerName: string;
dockerParams?: string[];
applicationParams?: string[];
}): Promise<string> {
const userInfo = os.userInfo();
const params = args.dockerParams ?? [];
const appParams = args.applicationParams ?? [];
if (userInfo.uid >= 0) {
// On *nix we run the docker container as our uid:gid otherwise cleaning it up its media_store can be difficult
params.push("-u", `${userInfo.uid}:${userInfo.gid}`);
}
return new Promise<string>((resolve, reject) => {
childProcess.execFile('docker', [
"run",
"--name", args.containerName,
"-d",
...params,
args.image,
... appParams
], (err, stdout) => {
if (err) {
reject(err);
}
resolve(stdout.trim());
});
});
}
export function dockerExec(args: {
containerId: string;
params: string[];
}): Promise<void> {
return new Promise<void>((resolve, reject) => {
childProcess.execFile("docker", [
"exec", args.containerId,
...args.params,
], { encoding: 'utf8' }, (err, stdout, stderr) => {
if (err) {
console.log(stdout);
console.log(stderr);
reject(err);
return;
}
resolve();
});
});
}
/**
* Create a docker network; does not fail if network already exists
*/
export function dockerCreateNetwork(args: {
networkName: string;
}): Promise<void> {
return new Promise<void>((resolve, reject) => {
childProcess.execFile("docker", [
"network",
"create",
args.networkName
], { encoding: 'utf8' }, (err, stdout, stderr) => {
if(err) {
if (stderr.includes(`network with name ${args.networkName} already exists`)) {
// Don't consider this as error
resolve();
}
reject(err);
return;
}
resolve();
})
});
}
export async function dockerLogs(args: {
containerId: string;
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",
args.containerId,
], {
stdio: ["ignore", stdoutFile, stderrFile],
}).once('close', resolve);
});
if (args.stdoutFile) await fse.close(<number>stdoutFile);
if (args.stderrFile) await fse.close(<number>stderrFile);
}
export function dockerStop(args: {
containerId: string;
}): Promise<void> {
return new Promise<void>((resolve, reject) => {
childProcess.execFile('docker', [
"stop",
args.containerId,
], err => {
if (err) {
reject(err);
}
resolve();
});
});
}
export function dockerRm(args: {
containerId: string;
}): Promise<void> {
return new Promise<void>((resolve, reject) => {
childProcess.execFile('docker', [
"rm",
args.containerId,
], err => {
if (err) {
reject(err);
}
resolve();
});
});
}

View File

@ -0,0 +1,203 @@
/*
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.
*/
/// <reference types="cypress" />
import * as path from "path";
import * as os from "os";
import * as crypto from "crypto";
import * as fse from "fs-extra";
import {dockerCreateNetwork, dockerExec, dockerLogs, dockerRun, dockerStop} from "../docker";
import {request} from "@playwright/test";
// A cypress plugins to add command to start & stop synapses in
// docker with preset templates.
interface SynapseConfig {
configDir: string;
registrationSecret: string;
// Synapse must be configured with its public_baseurl so we have to allocate a port & url at this stage
baseUrl: string;
port: number;
host: string;
}
export interface SynapseInstance extends SynapseConfig {
synapseId: string;
}
const synapses = new Map<string, SynapseInstance>();
function randB64Bytes(numBytes: number): string {
return crypto.randomBytes(numBytes).toString("base64").replace(/=*$/, "");
}
async function cfgDirFromTemplate(template: string): Promise<SynapseConfig> {
const templateDir = path.join(__dirname, "templates", template);
const stats = await fse.stat(templateDir);
if (!stats?.isDirectory) {
throw new Error(`No such template: ${template}`);
}
const tempDir = await fse.mkdtemp(path.join(os.tmpdir(), '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 synapseHost = process.env["SYNAPSE_IP_ADDRESS"]!!;
const synapsePort = parseInt(process.env["SYNAPSE_PORT"]!, 10);
const baseUrl = `http://${synapseHost}:${synapsePort}`;
// now copy homeserver.yaml, applying substitutions
console.log(`Gen ${path.join(templateDir, "homeserver.yaml")}`);
let hsYaml = await fse.readFile(path.join(templateDir, "homeserver.yaml"), "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);
const dexHost = process.env["DEX_IP_ADDRESS"];
const dexPort = process.env["DEX_PORT"];
const dexUrl = `http://${dexHost}:${dexPort}/dex`;
hsYaml = hsYaml.replace(/{{OIDC_ISSUER}}/g, dexUrl);
await fse.writeFile(path.join(tempDir, "homeserver.yaml"), 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);
console.log(`Gen ${path.join(templateDir, "localhost.signing.key")}`);
await fse.writeFile(path.join(tempDir, "localhost.signing.key"), `ed25519 x ${signingKey}`);
return {
port: synapsePort,
host: synapseHost,
baseUrl,
configDir: tempDir,
registrationSecret,
};
}
// Start a synapse instance: the template must be the name of
// one of the templates in the cypress/plugins/synapsedocker/templates
// directory
export async function synapseStart(template: string): Promise<SynapseInstance> {
const synCfg = await cfgDirFromTemplate(template);
console.log(`Starting synapse with config dir ${synCfg.configDir}...`);
await dockerCreateNetwork({ networkName: "hydrogen" });
const synapseId = await dockerRun({
image: "matrixdotorg/synapse:develop",
containerName: `hydrogen-synapse`,
dockerParams: [
"--rm",
"-v", `${synCfg.configDir}:/data`,
`--ip=${synCfg.host}`,
/**
* When using -p flag with --ip, the docker internal port must be used to access from the host
*/
"-p", `${synCfg.port}:8008/tcp`,
"--network=hydrogen",
],
applicationParams: [
"run"
]
});
console.log(`Started synapse with id ${synapseId} on port ${synCfg.port}.`);
// Await Synapse healthcheck
await dockerExec({
containerId: synapseId,
params: [
"curl",
"--connect-timeout", "30",
"--retry", "30",
"--retry-delay", "1",
"--retry-all-errors",
"--silent",
"http://localhost:8008/health",
],
});
const synapse: SynapseInstance = { synapseId, ...synCfg };
synapses.set(synapseId, synapse);
return synapse;
}
export async function synapseStop(id: string): Promise<void> {
const synCfg = synapses.get(id);
if (!synCfg) throw new Error("Unknown synapse ID");
const synapseLogsPath = path.join("playwright", "synapselogs", id);
await fse.ensureDir(synapseLogsPath);
await dockerLogs({
containerId: id,
stdoutFile: path.join(synapseLogsPath, "stdout.log"),
stderrFile: path.join(synapseLogsPath, "stderr.log"),
});
await dockerStop({
containerId: id,
});
await fse.remove(synCfg.configDir);
synapses.delete(id);
console.log(`Stopped synapse id ${id}.`);
}
interface Credentials {
accessToken: string;
userId: string;
deviceId: string;
homeServer: string;
}
export async function registerUser(synapse: SynapseInstance, username: string, password: string, displayName?: string,): Promise<Credentials> {
const url = `${synapse.baseUrl}/_synapse/admin/v1/register`;
const context = await request.newContext({ baseURL: url });
const { nonce } = await (await context.get(url)).json();
const mac = crypto.createHmac('sha1', synapse.registrationSecret).update(
`${nonce}\0${username}\0${password}\0notadmin`,
).digest('hex');
const response = await (await context.post(url, {
data: {
nonce,
username,
password,
mac,
admin: false,
displayname: displayName,
}
})).json();
return {
homeServer: response.home_server,
accessToken: response.access_token,
userId: response.user_id,
deviceId: response.device_id,
};
}

View File

@ -0,0 +1,3 @@
# Meta-template for synapse templates
To make another template, you can copy this directory

View 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

View 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

View File

@ -0,0 +1 @@
A synapse configured with user privacy consent enabled

View 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

View 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

View File

@ -0,0 +1,23 @@
<!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>

View File

@ -0,0 +1,9 @@
<!doctype html>
<html lang="en">
<head>
<title>Test Privacy policy</title>
</head>
<body>
<p>Danke schon</p>
</body>
</html>

View File

@ -0,0 +1 @@
A synapse configured with user privacy consent disabled

View File

@ -0,0 +1,76 @@
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"

View 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

View File

@ -0,0 +1,89 @@
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: dex
idp_name: "My Dex server"
skip_verification: true # This is needed as Dex is served on an insecure endpoint
issuer: "{{OIDC_ISSUER}}"
client_id: "synapse"
client_secret: "secret"
scopes: ["openid", "profile"]
user_mapping_provider:
config:
localpart_template: "{{ user.name }}"
display_name_template: "{{ user.name|capitalize }}"

View 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

View File

@ -0,0 +1,59 @@
/*
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 {test} from '@playwright/test';
import {synapseStart, synapseStop, registerUser} from "../plugins/synapsedocker";
import {dexStart, dexStop} from "../plugins/dex";
import type {DexInstance} from "../plugins/dex";
import type {SynapseInstance} from "../plugins/synapsedocker";
test.describe("Login", () => {
let synapse: SynapseInstance;
let dex: DexInstance;
test.beforeEach(async () => {
dex = await dexStart();
synapse = await synapseStart("sso");
});
test.afterEach(async () => {
await synapseStop(synapse.synapseId);
await dexStop(dex.dexId);
});
test("Login using username/password", async ({ page }) => {
const username = "foobaraccount";
const password = "password123";
await registerUser(synapse, username, password);
await page.goto("/");
await page.locator("#homeserver").fill("");
await page.locator("#homeserver").type(synapse.baseUrl);
await page.locator("#username").type(username);
await page.locator("#password").type(password);
await page.getByText('Log In', { exact: true }).click();
await page.locator(".SessionView").waitFor();
});
test("Login using SSO", async ({ page }) => {
await page.goto("/");
await page.locator("#homeserver").fill("");
await page.locator("#homeserver").type(synapse.baseUrl);
await page.locator(".StartSSOLoginView_button").click();
await page.getByText("Log in with Example").click();
await page.locator(".dex-btn-text", {hasText: "Grant Access"}).click();
await page.locator(".primary-button", {hasText: "Continue"}).click();
await page.locator(".SessionView").waitFor();
});
});

View File

@ -0,0 +1,21 @@
/*
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 {test} from '@playwright/test';
test("App has no startup errors that prevent UI render", async ({ page }) => {
await page.goto("/");
await page.getByText("Log In", { exact: true }).waitFor();
});

19
scripts/test-app.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
# Make sure docker is available
if ! docker info > /dev/null 2>&1; then
echo "You need to intall docker before you can run the tests!"
exit 1
fi
# Stop running containers
if docker stop hydrogen-synapse > /dev/null 2>&1; then
echo "Existing 'hydrogen-synapse' container stopped ✔"
fi
if docker stop hydrogen-dex > /dev/null 2>&1; then
echo "Existing 'hydrogen-dex' container stopped ✔"
fi
# Run playwright
yarn playwright test

926
yarn.lock

File diff suppressed because it is too large Load Diff