@samitouri / QOSami-HFS / commits / 4c72f017

dx: steadier tests

Massimo Melina committed Jun 13, 2025 at 19:48 UTC 4c72f017130b1b38995bda08dd8ba2f1ae881d84
4 files changed +96 -87
e2e/common.ts new
+20
@@ -0,0 +1,20 @@
1 +import { test } from '@playwright/test'
2 +import fs from 'fs'
3 +
4 +export const username = 'rejetto'
5 +export const password = 'password'
6 +export const URL = 'http://localhost:81/'
7 +export const uploadName = 'uploaded'
8 +
9 +const t = Date.UTC(2025, 0, 20, 3, 0, 0, 0) / 1000 // a fixed timestamp, for visual comparison
10 +
11 +test.beforeAll(clearUploads)
12 +
13 +export function clearUploads() {
14 + fs.unlink('tests/' + uploadName, () => {});
15 + resetTimestamp()
16 +}
17 +
18 +export function resetTimestamp() {
19 + fs.utimesSync('tests', t, t);
20 +}
\ No newline at end of file
e2e/frontend.spec.ts
+2 -86
@@ -1,25 +1,7 @@
1 import { test, expect, Page } from '@playwright/test'
2 import fs from 'fs'
3 import { wait } from '../src/cross'
4 -
5 -const username = 'rejetto'
6 -const password = 'password'
7 -const fileToUpload = 'dev-plugins.md'
8 -const uploadName = 'uploaded'
9 -const URL = 'http://localhost:81/'
10 -
11 -const t = Date.UTC(2025, 0, 20, 3, 0, 0, 0) / 1000 // a fixed timestamp, for visual comparison
12 -
13 -test.beforeAll(clearUploads)
14 -
15 -function clearUploads() {
16 - fs.unlink('tests/' + uploadName, () => {});
17 - resetTimestamp()
18 -}
19 -
20 -function resetTimestamp() {
21 - fs.utimesSync('tests', t, t);
22 -}
4 +import { password, resetTimestamp, URL, username } from './common'
5
6 // a generic test touch several parts
7 test('around1', async ({ page }) => {
@@ -133,51 +115,8 @@ test('around1', async ({ page }) => {
115 await expect(page.getByText('file, 10 folders, 6 B')).toBeVisible();
116 });
117
136 -test('upload1', async ({ page, context, browserName, browser }) => {
137 - if (page.viewportSize()?.width! < 1000 || browserName !== 'chromium') return // test only for desktop, as safari has no cdpSession, and to disconnect i need only 1 upload at a time
138 - await page.goto(URL);
139 - await page.getByRole('button', { name: 'Login' }).click();
140 - await page.getByRole('textbox', { name: 'Username' }).fill(username);
141 - await page.getByRole('textbox', { name: 'Username' }).press('Tab');
142 - await page.getByRole('textbox', { name: 'Password' }).fill(password);
143 - await page.getByRole('button', { name: 'Continue' }).click();
144 - await page.locator('div').filter({ hasText: 'Logged in' }).nth(3).click();
145 -
146 - await page.getByRole('link', { name: 'for-admins, Folder' }).click();
147 - await page.getByRole('link', { name: 'upload, Folder' }).click();
148 -
149 - await page.getByRole('button', { name: 'Options' }).click();
150 - const pageAdminPromise = page.waitForEvent('popup');
151 - await page.getByRole('button', { name: 'Admin-panel' }).click();
152 - const pageAdmin = await pageAdminPromise;
153 - await pageAdmin.goto(URL + '~/admin/#/monitoring'); // cross-device way of changing page
154 - await page.locator('div').filter({ hasText: 'xOptionsAdmin-panelSort by:' }).nth(2).click();
155 - await page.getByRole('button', { name: 'Close' }).click();
156 - await page.getByRole('button', { name: 'Upload' }).click();
157 - const fileChooserPromise = page.waitForEvent('filechooser');
158 - await page.getByRole('button', { name: 'Pick files' }).click();
159 - const fileChooser = await fileChooserPromise;
160 - await fileChooser.setFiles(fileToUpload);
161 - const cdpSession = await context.newCDPSession(page)
162 - await cdpSession.send('Network.emulateNetworkConditions', NETWORK_PRESETS.Regular2G)
163 - await page.getByRole('button', { name: 'Edit' }).click();
164 - await page.getByRole('textbox').fill(uploadName);
165 - await page.getByRole('button', { name: 'Continue' }).click();
166 - await page.getByRole('button', { name: 'Send 1 file' }).click();
167 - await wait(2000)
168 - await pageAdmin.getByRole('cell', { name: uploadName }).click();
169 - await pageAdmin.getByRole('button', { name: '(Disconnect)' }).click();
170 - await pageAdmin.getByRole('button', { name: '(Close)' }).click();
171 - await pageAdmin.close()
172 - await page.getByText('Copy links').click();
173 - await page.getByText('Operation successful').click();
174 - await page.getByRole('button', { name: 'Close' }).click();
175 - await cdpSession?.send('Network.emulateNetworkConditions', NETWORK_PRESETS.NoThrottle)
176 - clearUploads()
177 -});
178 -
118 test('search1', async ({ page }) => {
180 - fs.utimesSync('tests', t, t)
119 + resetTimestamp()
120 await page.goto(URL);
121 await page.getByRole('button', { name: 'Search' }).click();
122 await page.locator('input[name="name"]').fill('a');
@@ -350,26 +289,3 @@ async function screenshot(page: Page, selectorForMask='') {
289 await wait(1000) // this accounts especially for our DataTable component which takes time to set the layout
290 return expect(page).toHaveScreenshot({ fullPage: true, mask: [page.locator(`.maskInTests${selectorForMask}`)] });
291 }
353 -
354 -const NETWORK_PRESETS = {
355 - Offline: {
356 - offline: true,
357 - downloadThroughput: 0,
358 - uploadThroughput: 0,
359 - latency: 0,
360 - connectionType: 'none',
361 - },
362 - NoThrottle: {
363 - offline: false,
364 - downloadThroughput: -1,
365 - uploadThroughput: -1,
366 - latency: 0,
367 - },
368 - Regular2G: {
369 - offline: false,
370 - downloadThroughput: (250 * 1024) / 8,
371 - uploadThroughput: (120 * 1024) / 8,
372 - latency: 300,
373 - connectionType: 'cellular2g',
374 - },
375 -} as const;
e2e/serial.spec.ts new
+73
@@ -0,0 +1,73 @@
1 +import { test } from '@playwright/test'
2 +import { wait } from '../src/cross'
3 +import { clearUploads, password, uploadName, URL, username } from './common'
4 +
5 +// this test is separated to run serially, as it will modify folder timestamp for a few seconds, during which other tests may fail
6 +
7 +export const fileToUpload = 'dev-plugins.md'
8 +
9 +test('upload1', async ({ page, context, browserName }) => {
10 + if (page.viewportSize()?.width! < 1000 || browserName !== 'chromium') return // test only for desktop, as safari has no cdpSession, and to disconnect i need only 1 upload at a time
11 + await page.goto(URL);
12 + await page.getByRole('button', { name: 'Login' }).click();
13 + await page.getByRole('textbox', { name: 'Username' }).fill(username);
14 + await page.getByRole('textbox', { name: 'Username' }).press('Tab');
15 + await page.getByRole('textbox', { name: 'Password' }).fill(password);
16 + await page.getByRole('button', { name: 'Continue' }).click();
17 + await page.locator('div').filter({ hasText: 'Logged in' }).nth(3).click();
18 +
19 + await page.getByRole('link', { name: 'for-admins, Folder' }).click();
20 + await page.getByRole('link', { name: 'upload, Folder' }).click();
21 +
22 + await page.getByRole('button', { name: 'Options' }).click();
23 + const pageAdminPromise = page.waitForEvent('popup');
24 + await page.getByRole('button', { name: 'Admin-panel' }).click();
25 + const pageAdmin = await pageAdminPromise;
26 + await pageAdmin.goto(URL + '~/admin/#/monitoring'); // cross-device way of changing page
27 + await page.locator('div').filter({ hasText: 'xOptionsAdmin-panelSort by:' }).nth(2).click();
28 + await page.getByRole('button', { name: 'Close' }).click();
29 + await page.getByRole('button', { name: 'Upload' }).click();
30 + const fileChooserPromise = page.waitForEvent('filechooser');
31 + await page.getByRole('button', { name: 'Pick files' }).click();
32 + const fileChooser = await fileChooserPromise;
33 + await fileChooser.setFiles(fileToUpload);
34 + const cdpSession = await context.newCDPSession(page)
35 + await cdpSession.send('Network.emulateNetworkConditions', NETWORK_PRESETS.Regular2G)
36 + await page.getByRole('button', { name: 'Edit' }).click();
37 + await page.getByRole('textbox').fill(uploadName);
38 + await page.getByRole('button', { name: 'Continue' }).click();
39 + await page.getByRole('button', { name: 'Send 1 file' }).click();
40 + await wait(2000)
41 + await pageAdmin.getByRole('cell', { name: uploadName }).click();
42 + await pageAdmin.getByRole('button', { name: '(Disconnect)' }).click();
43 + await pageAdmin.getByRole('button', { name: '(Close)' }).click();
44 + await pageAdmin.close()
45 + await page.getByText('Copy links').click();
46 + await page.getByText('Operation successful').click();
47 + await page.getByRole('button', { name: 'Close' }).click();
48 + await cdpSession?.send('Network.emulateNetworkConditions', NETWORK_PRESETS.NoThrottle)
49 + clearUploads()
50 +});
51 +
52 +const NETWORK_PRESETS = {
53 + Offline: {
54 + offline: true,
55 + downloadThroughput: 0,
56 + uploadThroughput: 0,
57 + latency: 0,
58 + connectionType: 'none',
59 + },
60 + NoThrottle: {
61 + offline: false,
62 + downloadThroughput: -1,
63 + uploadThroughput: -1,
64 + latency: 0,
65 + },
66 + Regular2G: {
67 + offline: false,
68 + downloadThroughput: (250 * 1024) / 8,
69 + uploadThroughput: (120 * 1024) / 8,
70 + latency: 300,
71 + connectionType: 'cellular2g',
72 + },
73 +} as const;
package.json
+1 -1
@@ -22,7 +22,7 @@
22 "test-with-server": "rm -rf tests/work && (node dist/src --cwd tests/work --config tests & echo $! > .server_pid) && sleep 2 && node --import tsx --test tests/test.ts; _exit=$?; SERVER_PID=$(cat ./.server_pid); kill \"$SERVER_PID\" 2>/dev/null || true; rm -f ./.server_pid; exit $_exit",
23 "test-ui": "npx playwright test --ui",
24 "pub": "cd dist && npm publish",
25 - "dist": "npm run build-all && npx playwright test && npm run dist-bin",
25 + "dist": "npm run build-all && npx playwright test frontend && npx playwright test serial && npm run dist-bin",
26 "dist-bin": "npm run dist-modules && npm run dist-bin-win && npm run dist-bin-linux && npm run dist-bin-mac && npm run dist-bin-mac-arm",
27 "dist-modules": "cp package*.json central.json dist && cd dist && npm ci --omit=dev && cd .. && node prune_modules",
28 "dist-pre": "cd dist && rm -rf node_modules/@node-rs/crc32-*",