Add integration test for call decline

This commit is contained in:
Miriam Zimmerman 2025-07-11 11:48:31 -04:00 committed by GitHub
parent cf03754d2f
commit b95161859e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 158 additions and 10 deletions

View file

@ -429,7 +429,8 @@ jobs:
run: |
set -o pipefail
xvfb-run --auto-servernum pnpm run test-mock
timeout-minutes: 10
xvfb-run --auto-servernum pnpm run test-mock-docker
timeout-minutes: 15
env:
NODE_ENV: production
DEBUG: mock:test:*

View file

@ -48,6 +48,7 @@
"test-release": "node ts/scripts/test-release.js",
"test-node": "cross-env LANG=en-us electron-mocha --timeout 10000 --main test/fix-linux-gtk.js --file test/setup-test-node.js --recursive ts/test-node",
"test-mock": "mocha --require ts/test-mock/setup-ci.js ts/test-mock/**/*_test.js",
"test-mock-docker": "mocha --require ts/test-mock/setup-ci.js ts/test-mock/**/*_test.docker.js",
"test-eslint": "mocha .eslint/rules/**/*.test.js --ignore-leaks",
"test-lint-intl": "ts-node ./build/intl-linter/linter.ts --test",
"eslint": "eslint --cache . --cache-strategy content --max-warnings 0",
@ -222,7 +223,7 @@
"@indutny/parallel-prettier": "3.0.0",
"@indutny/rezip-electron": "2.0.1",
"@napi-rs/canvas": "0.1.61",
"@signalapp/mock-server": "13.1.0",
"@signalapp/mock-server": "13.2.0",
"@storybook/addon-a11y": "8.4.4",
"@storybook/addon-actions": "8.4.4",
"@storybook/addon-controls": "8.4.4",

10
pnpm-lock.yaml generated
View file

@ -430,8 +430,8 @@ importers:
specifier: 0.1.61
version: 0.1.61
'@signalapp/mock-server':
specifier: 13.1.0
version: 13.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)
specifier: 13.2.0
version: 13.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)
'@storybook/addon-a11y':
specifier: 8.4.4
version: 8.4.4(storybook@8.4.4(bufferutil@4.0.9)(prettier@3.3.3)(utf-8-validate@5.0.10))
@ -2770,8 +2770,8 @@ packages:
'@signalapp/libsignal-client@0.76.3':
resolution: {integrity: sha512-Ht8XtdsSvgiCb8ftUYE9DaLcWy0vltrj9cQ2sfy+DGUayE1k2njicNhB2RKOfQV2Wb/1Cl0WxVZP/NlXRo2+jA==}
'@signalapp/mock-server@13.1.0':
resolution: {integrity: sha512-CuDNLNEBMzwIs5jr7Lx9F4YFoRD62s7WgPGtm3qpaggixSQtabjMC7AKSR0xvaHcZpYZtBU5jcGK8Roguo9nuw==}
'@signalapp/mock-server@13.2.0':
resolution: {integrity: sha512-f5uxzsIwPmkevX5ycCRWgqy/VCbvj/dUA8yGWlfO2hFc62UZ4FaOY0n5YFMOpQgp5eMO6TMgr1KqIR/uAxfkIg==}
'@signalapp/parchment-cjs@3.0.1':
resolution: {integrity: sha512-hSBMQ1M7wE4GcC8ZeNtvpJF+DAJg3eIRRf1SiHS3I3Algav/sgJJNm6HIYm6muHuK7IJmuEjkL3ILSXgmu0RfQ==}
@ -12466,7 +12466,7 @@ snapshots:
type-fest: 4.26.1
uuid: 11.0.2
'@signalapp/mock-server@13.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)':
'@signalapp/mock-server@13.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)':
dependencies:
'@indutny/parallel-prettier': 3.0.0(prettier@3.3.3)
'@signalapp/libsignal-client': 0.60.2

View file

@ -0,0 +1,110 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { StorageState } from '@signalapp/mock-server';
import { expect } from 'playwright/test';
import * as durations from '../../util/durations';
import type { App } from '../playwright';
import { Bootstrap } from '../bootstrap';
import { runTurnInContainer, tearDownTurnContainer } from './helpers';
describe('callMessages', function callMessages(this: Mocha.Suite) {
this.timeout(durations.MINUTE);
let bootstrap1: Bootstrap;
let bootstrap2: Bootstrap;
let app1: App;
let app2: App;
beforeEach(async () => {
runTurnInContainer();
bootstrap1 = new Bootstrap();
await bootstrap1.init();
bootstrap2 = new Bootstrap({ server: bootstrap1.server });
await bootstrap2.init();
let state1 = StorageState.getEmpty();
state1 = state1.updateAccount({
profileKey: bootstrap1.phone.profileKey.serialize(),
});
state1 = state1.addContact(bootstrap2.phone, {
whitelisted: true,
profileKey: bootstrap2.phone.profileKey.serialize(),
givenName: 'Contact2',
});
state1 = state1.pin(bootstrap2.phone);
await bootstrap1.phone.setStorageState(state1);
app1 = await bootstrap1.link();
let state2 = StorageState.getEmpty();
state2 = state2.updateAccount({
profileKey: bootstrap2.phone.profileKey.serialize(),
});
state2 = state2.addContact(bootstrap1.phone, {
whitelisted: true,
profileKey: bootstrap1.phone.profileKey.serialize(),
givenName: 'Contact1',
});
state2 = state2.pin(bootstrap1.phone);
await bootstrap2.phone.setStorageState(state2);
app2 = await bootstrap2.link();
});
afterEach(async function after(this: Mocha.Context) {
tearDownTurnContainer();
if (!bootstrap1) {
return;
}
await bootstrap1.maybeSaveLogs(this.currentTest, app1);
await bootstrap2.maybeSaveLogs(this.currentTest, app2);
await app2.close();
await app1.close();
await bootstrap2.teardown();
await bootstrap1.teardown();
});
it('can call and decline a call', async () => {
const window1 = await app1.getWindow();
const leftPane1 = window1.locator('#LeftPane');
await leftPane1
.locator(`[data-testid="${bootstrap2.phone.device.aci}"]`)
.click();
// Try to start a call
await window1.locator('.module-ConversationHeader__button--audio').click();
const window1Permissions = await app1.waitForWindow();
await window1Permissions.getByText('Allow Access').click();
await window1
.locator('.CallingLobbyJoinButton')
.and(window1.locator('button:visible'))
.click();
const window2 = await app2.getWindow();
// Only wait for 3 seconds to make sure that this succeeded properly rather
// than timing out after ~10 seconds and using a direct connection
await window2
.locator('.IncomingCallBar__button--decline')
.click({ timeout: 3000 });
await expect(
window1.locator('.module-calling__modal-container')
).toBeEmpty();
await expect(
window2.locator('.module-calling__modal-container')
).toBeEmpty();
});
});

View file

@ -0,0 +1,32 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as child from 'child_process';
import createDebug from 'debug';
const debug = createDebug('mock:test:calling:helpers');
export function runTurnInContainer(): void {
tearDownTurnContainer();
const result = child.spawnSync('docker', [
'run',
'--name',
'coturn',
'-d',
'--network=host',
'coturn/coturn',
]);
debug(
'create coturn: signal: ',
result.signal,
' status: ',
result.status,
'stderr: ',
result.stderr?.toString()
);
}
export function tearDownTurnContainer(): void {
debug('tearDownTurnContainer');
child.spawnSync('docker', ['rm', '--force', '--volumes', 'coturn']);
}

View file

@ -7,7 +7,7 @@ import type { App } from '../playwright';
import { Bootstrap } from '../bootstrap';
import { typeIntoInput, waitForEnabledComposer } from '../helpers';
describe('callMessages', function (this: Mocha.Suite) {
describe('twoClients', function twoClients(this: Mocha.Suite) {
this.timeout(durations.MINUTE);
let bootstrap1: Bootstrap;
@ -56,12 +56,12 @@ describe('callMessages', function (this: Mocha.Suite) {
app2 = await bootstrap2.link();
});
afterEach(async function (this: Mocha.Context) {
afterEach(async function after(this: Mocha.Context) {
if (!bootstrap1) {
return;
}
await bootstrap2.maybeSaveLogs(this.currentTest, app2);
await bootstrap1.maybeSaveLogs(this.currentTest, app1);
await bootstrap2.maybeSaveLogs(this.currentTest, app2);
await app2.close();
await app1.close();

View file

@ -145,6 +145,10 @@ export class App extends EventEmitter {
return this.#waitForEvent('storageServiceComplete');
}
public async waitForWindow(): Promise<Page> {
return this.#app.waitForEvent('window');
}
public async waitForManifestVersion(version: number): Promise<void> {
// eslint-disable-next-line no-constant-condition
while (true) {