Skip to content

Commit 4761a4b

Browse files
committed
tests: separate backend/simulator logs from Playwright logs.
Backend and simulator logs in the Github Action's logs are confusing, hard to scroll and get mixed with Playwright's logs. Instead, we redirect them to file and upload them as artifacts.
1 parent 8df9249 commit 4761a4b

File tree

8 files changed

+89
-26
lines changed

8 files changed

+89
-26
lines changed

.github/workflows/playwright.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,9 @@ jobs:
9494
SIMULATOR_PATH: ${{ github.workspace }}/simulator-bin/simulator
9595
run: make webe2etest
9696

97-
- name: Upload Playwright artifacts
98-
if: failure()
97+
- name: Upload Playwright artifacts and logs.
98+
if: always()
9999
uses: actions/upload-artifact@v4
100100
with:
101-
name: playwright-artifacts
101+
name: playwright-artifacts-and-logs
102102
path: frontends/web/test-results/*

frontends/web/tests/base.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ import { expect } from '@playwright/test';
2020

2121
let servewallet: ServeWallet;
2222

23-
test('App main page loads', async ({ page, host, frontendPort, servewalletPort }) => {
23+
test('App main page loads', async ({ page, host, frontendPort, servewalletPort }, testInfo) => {
2424

2525
await test.step('Start servewallet', async () => {
26-
servewallet = new ServeWallet(page, servewalletPort, frontendPort, host);
26+
servewallet = new ServeWallet(page, servewalletPort, frontendPort, host, testInfo.title, testInfo.project.name);
2727
await servewallet.start();
2828
});
2929

frontends/web/tests/gapSettings.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ import { deleteAccountsFile } from './helpers/fs';
2424

2525
let servewallet: ServeWallet;
2626

27-
test('Gap limits are correctly saved', async ({ page, host, frontendPort, servewalletPort }) => {
27+
test('Gap limits are correctly saved', async ({ page, host, frontendPort, servewalletPort }, testInfo) => {
2828

2929
await test.step('Start servewallet', async () => {
30-
servewallet = new ServeWallet(page, servewalletPort, frontendPort, host);
30+
servewallet = new ServeWallet(page, servewalletPort, frontendPort, host, testInfo.title, testInfo.project.name);
3131
await servewallet.start();
3232
});
3333

frontends/web/tests/helpers/fs.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,28 @@ function deleteFile(filePath: string) {
3434
} else {
3535
console.warn(`File ${filePath} does not exist, skipping removal.`);
3636
}
37+
38+
}
39+
40+
function sanitizeFileName(name: string): string {
41+
return name.replace(/\s+/g, '_').replace(/[^a-zA-Z0-9_-]/g, '');
42+
}
43+
44+
45+
/**
46+
* Returns a full path for a log file in test-results/<test>-<project>/
47+
* Automatically creates the folder if it doesn't exist.
48+
*
49+
* @param testName - Current test name
50+
* @param projectName - Playwright project name
51+
* @param logFileName - The log filename (e.g., 'backend.log' or 'simulator.log')
52+
*/
53+
export function getLogFilePath(testName: string, projectName: string, logFileName: string): string {
54+
const safeTest = sanitizeFileName(testName);
55+
const safeProject = sanitizeFileName(projectName);
56+
57+
const folderPath = path.resolve(process.cwd(), 'test-results', `${safeTest}-${safeProject}`);
58+
fs.mkdirSync(folderPath, { recursive: true });
59+
60+
return path.join(folderPath, logFileName);
3761
}

frontends/web/tests/helpers/servewallet.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
import { spawn, ChildProcess } from 'child_process';
1818
import * as net from 'net';
19+
import * as fs from 'fs';
1920
import type { Page } from '@playwright/test';
21+
import { getLogFilePath } from './fs';
2022

2123
async function connectOnce(host: string, port: number): Promise<void> {
2224
return new Promise((resolve, reject) => {
@@ -36,19 +38,25 @@ export interface ServeWalletOptions {
3638

3739
export class ServeWallet {
3840
private proc?: ChildProcess;
41+
private outStream?: number;
3942
private readonly simulator: boolean;
4043
private readonly page: Page;
4144
private readonly servewalletPort: number;
4245
private readonly frontendPort: number;
4346
private readonly host: string;
4447
private readonly timeout: number;
4548
private readonly testnet: boolean;
49+
private readonly testName: string;
50+
private readonly projectName: string;
51+
private readonly logPath: string;
4652

4753
constructor(
4854
page: Page,
4955
servewalletPort: number,
5056
frontendPort: number,
5157
host: string,
58+
testName: string,
59+
projectName: string,
5260
options: ServeWalletOptions = {}
5361
) {
5462
const { simulator = false, timeout = 90000, testnet = true } = options;
@@ -64,11 +72,24 @@ export class ServeWallet {
6472
this.simulator = simulator;
6573
this.timeout = timeout;
6674
this.testnet = testnet;
75+
this.testName = testName;
76+
this.projectName = projectName;
77+
78+
this.logPath = getLogFilePath(this.testName, this.projectName, 'servewallet.log');
79+
this.openOutStream(false); // On the first time, open the file in "w" mode.
80+
}
81+
82+
private openOutStream(append: boolean): void {
83+
if (this.outStream) {
84+
fs.closeSync(this.outStream);
85+
}
86+
this.outStream = fs.openSync(this.logPath, append ? 'a' : 'w');
6787
}
6888

6989
async start(): Promise<void> {
70-
let target: string;
90+
this.openOutStream(true); // On starts/restarts, open the file in "a" mode.
7191

92+
let target: string;
7293
if (this.testnet && !this.simulator) {
7394
target = 'servewallet';
7495
} else if (this.testnet && this.simulator) {
@@ -81,7 +102,7 @@ export class ServeWallet {
81102
}
82103

83104
this.proc = spawn('make', ['-C', '../../', target], {
84-
stdio: 'inherit',
105+
stdio: ['ignore', this.outStream, this.outStream],
85106
detached: true,
86107
});
87108

@@ -100,7 +121,7 @@ export class ServeWallet {
100121
);
101122
return;
102123
} catch {
103-
// page.goto failed, likely connection refused; retry
124+
// page.goto failed; likely connection refused; retry
104125
}
105126
} catch {
106127
// port not ready yet
@@ -124,6 +145,19 @@ export class ServeWallet {
124145
// Listen for exit event
125146
this.proc.once('exit', () => {
126147
console.log('Servewallet stopped');
148+
this.proc = undefined;
149+
150+
if (this.outStream) {
151+
try {
152+
fs.closeSync(this.outStream);
153+
console.log('Servewallet log file closed');
154+
} catch (err) {
155+
console.warn('Failed to close servewallet log file:', err);
156+
} finally {
157+
this.outStream = undefined;
158+
}
159+
}
160+
127161
resolve();
128162
});
129163

frontends/web/tests/helpers/simulator.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
*/
1616

1717

18-
import { spawn, ChildProcessWithoutNullStreams } from 'child_process';
18+
import { spawn, ChildProcess } from 'child_process';
1919
import type { Page } from '@playwright/test';
2020
import { clickButtonWithText, typeIntoFocusedInput, clickAllAgreements } from './dom';
21+
import { getLogFilePath } from './fs';
2122
import * as fs from 'fs';
2223
import * as path from 'path';
2324

@@ -55,30 +56,34 @@ export function cleanFakeMemoryFiles() {
5556
*/
5657
export function startSimulator(
5758
simulatorPath: string,
59+
testName: string,
60+
projectName: string,
5861
useFakeMemory = false
59-
): ChildProcessWithoutNullStreams {
62+
): ChildProcess {
6063
const env = { ...process.env };
6164
if (useFakeMemory) {
6265
env.FAKE_MEMORY_FILEPATH = '/tmp/fake_memory';
6366
}
6467

65-
const proc = spawn(simulatorPath, { stdio: 'pipe', env });
6668

67-
// Pipe output to logs (needed in CI)
68-
proc.stdout?.on('data', (chunk) => process.stdout.write(chunk));
69-
proc.stderr?.on('data', (chunk) => process.stderr.write(chunk));
69+
const logPath = getLogFilePath(testName, projectName, 'simulator.log');
70+
const outStream = fs.openSync(logPath, 'w');
71+
72+
const proc = spawn(simulatorPath, { stdio: ['ignore', outStream, outStream], env });
7073

7174
proc.on('error', (err) => {
7275
console.error('Simulator process error:', String(err));
7376
});
7477

7578
proc.on('exit', (code, signal) => {
7679
console.log(`Simulator exited: code=${String(code)}, signal=${String(signal)}`);
80+
fs.closeSync(outStream); // close log file when simulator exits
7781
});
7882

7983
return proc;
8084
}
8185

86+
8287
/**
8388
* Performs the wallet setup flow in order:
8489
* 1. Click "Continue"

frontends/web/tests/testnetRestart.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ let servewallet: ServeWallet;
3535
* - Kill servewallet
3636
* - Restart servewallet in mainnet mode - testnet mode should be disabled now.
3737
*/
38-
test('Testnet mode', async ({ page, host, frontendPort, servewalletPort }) => {
38+
test('Testnet mode', async ({ page, host, frontendPort, servewalletPort }, testInfo) => {
3939

4040
await test.step('Start servewallet', async () => {
41-
servewallet = new ServeWallet(page, servewalletPort, frontendPort, host, { testnet: false });
41+
servewallet = new ServeWallet(page, servewalletPort, frontendPort, host, testInfo.title, testInfo.project.name, { testnet: false });
4242
await servewallet.start();
4343
});
4444

frontends/web/tests/watch-only.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ import { test } from './helpers/fixtures';
2020
import { ServeWallet } from './helpers/servewallet';
2121
import { startSimulator, completeWalletSetupFlow, cleanFakeMemoryFiles } from './helpers/simulator';
2222
import { assertFieldsCount, clickButtonWithText } from './helpers/dom';
23-
import { ChildProcessWithoutNullStreams } from 'child_process';
23+
import { ChildProcess } from 'child_process';
2424

2525
let servewallet: ServeWallet;
26-
let simulatorProc : ChildProcessWithoutNullStreams | undefined;
26+
let simulatorProc : ChildProcess | undefined;
2727

2828
/**
2929
* Test scenario 1:
@@ -34,9 +34,9 @@ let simulatorProc : ChildProcessWithoutNullStreams | undefined;
3434
* - Restart app (kill and restart servewallet)
3535
* - Check that accounts do not show up without simulator running.
3636
*/
37-
test('Test #1 - No passphrase and no watch-only', async ({ page, host, frontendPort, servewalletPort }) => {
37+
test('Test #1 - No passphrase and no watch-only', async ({ page, host, frontendPort, servewalletPort }, testInfo) => {
3838
await test.step('Start servewallet', async () => {
39-
servewallet = new ServeWallet(page, servewalletPort, frontendPort, host, { simulator: true });
39+
servewallet = new ServeWallet(page, servewalletPort, frontendPort, host, testInfo.title, testInfo.project.name, { simulator: true });
4040
await servewallet.start();
4141
});
4242

@@ -46,7 +46,7 @@ test('Test #1 - No passphrase and no watch-only', async ({ page, host, frontendP
4646
throw new Error('SIMULATOR_PATH environment variable not set');
4747
}
4848

49-
simulatorProc = startSimulator(simulatorPath, true);
49+
simulatorProc = startSimulator(simulatorPath, testInfo.title, testInfo.project.name, true);
5050
console.log('Simulator started');
5151
});
5252

@@ -88,9 +88,9 @@ test('Test #1 - No passphrase and no watch-only', async ({ page, host, frontendP
8888
* - Restart app (kill and restart servewallet)
8989
* - Check that watch-only accounts still show up (with no simulator running)
9090
*/
91-
test('Test #2 - No passphrase - Watch-only account', async ({ page, host, frontendPort, servewalletPort }) => {
91+
test('Test #2 - No passphrase - Watch-only account', async ({ page, host, frontendPort, servewalletPort }, testInfo) => {
9292
await test.step('Start servewallet', async () => {
93-
servewallet = new ServeWallet(page, servewalletPort, frontendPort, host, { simulator: true });
93+
servewallet = new ServeWallet(page, servewalletPort, frontendPort, host, testInfo.title, testInfo.project.name, { simulator: true });
9494
await servewallet.start();
9595
});
9696

@@ -101,7 +101,7 @@ test('Test #2 - No passphrase - Watch-only account', async ({ page, host, fronte
101101
throw new Error('SIMULATOR_PATH environment variable not set');
102102
}
103103

104-
simulatorProc = startSimulator(simulatorPath, true);
104+
simulatorProc = startSimulator(simulatorPath, testInfo.title, testInfo.project.name, true);
105105

106106
console.log('Simulator started');
107107
});

0 commit comments

Comments
 (0)