@samitouri / QOSami-HFS / commits / a1c99597

test: frontend upload

Massimo Melina committed Jun 9, 2025 at 22:14 UTC a1c99597ca01a712d38f957721ab2ed890297bf4
3 files changed +89 -10
e2e/frontend.spec.ts
+87 -8
@@ -4,13 +4,27 @@ 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 +}
23 +
24 // a generic test touch several parts
25 test('around1', async ({ page }) => {
12 - fs.utimesSync('tests', t, t);
13 - await page.goto('http://localhost:81/');
26 + resetTimestamp();
27 + await page.goto(URL);
28 await expect(page).toHaveTitle(/File server/);
29 await screenshot(page);
30 await page.getByRole('button', { name: 'Login' }).click();
@@ -39,8 +53,8 @@ test('around1', async ({ page }) => {
53 await page.getByRole('checkbox', { name: 'alfa.txt' }).check();
54 await expect(page.getByRole('button', { name: 'Delete' })).toBeEnabled();
55 await page.getByRole('button', { name: 'Select' }).click();
42 - await page.getByRole('link', { name: 'home' }).click();
56
57 + await page.getByRole('link', { name: 'home' }).click();
58 await page.getByRole('button', { name: username }).click();
59 await page.getByRole('button', { name: 'Logout' }).click();
60 await page.getByText('Logged out').click();
@@ -119,9 +133,52 @@ test('around1', async ({ page }) => {
133 await expect(page.getByText('file, 10 folders, 6 B')).toBeVisible();
134 });
135
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 +
179 test('search1', async ({ page }) => {
180 fs.utimesSync('tests', t, t)
124 - await page.goto('http://localhost:81/');
181 + await page.goto(URL);
182 await page.getByRole('button', { name: 'Search' }).click();
183 await page.locator('input[name="name"]').fill('a');
184 await page.getByRole('button', { name: 'Continue' }).click();
@@ -177,7 +234,7 @@ test('search1', async ({ page }) => {
234 });
235
236 test('frontend-admin', async ({ page }) => {
180 - await page.goto('http://localhost:81/');
237 + await page.goto(URL);
238 await page.getByRole('button', { name: 'Options' }).click();
239 // no admin button yet,
240 await expect(page.getByRole('dialog')).toMatchAriaSnapshot(`
@@ -217,7 +274,7 @@ test('frontend-admin', async ({ page }) => {
274
275 test('admin1', async ({ page }) => {
276 await fs.promises.rm('tests/work/logs', {force: true, recursive: true}); // clear logs to have consistent screenshots
220 - await page.goto('http://localhost:81/~/admin/');
277 + await page.goto(URL + '~/admin/');
278 await page.getByRole('textbox', { name: 'Username' }).fill(username);
279 await page.getByRole('textbox', { name: 'Password' }).fill(password);
280 await page.getByRole('textbox', { name: 'Password' }).press('Enter');
@@ -237,7 +294,6 @@ test('admin1', async ({ page }) => {
294 function dataTableLoading() {
295 return expect(page.getByRole('grid').getByRole('img')).toBeVisible({ visible: false });
296 }
240 -// const dataTableContent = '.MuiDataGrid-overlayWrapperInner,.MuiDataGrid-virtualScroller'
297
298 await clickMenu('Internet'); // initiate the get_nat process, so we'll have to wait less, later
299 await clickMenu('Shared files')
@@ -293,4 +349,27 @@ async function screenshot(page: Page, selectorForMask='') {
349 selectorForMask = ',' + selectorForMask
350 await wait(1000) // this accounts especially for our DataTable component which takes time to set the layout
351 return expect(page).toHaveScreenshot({ fullPage: true, mask: [page.locator(`.maskInTests${selectorForMask}`)] });
296 -}
\ No newline at end of file
352 +}
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;
playwright.config.ts
+1 -1
@@ -13,7 +13,7 @@ import { defineConfig, devices } from '@playwright/test';
13 */
14 export default defineConfig({
15 testDir: './e2e',
16 - timeout: 20_000,
16 + timeout: 25_000,
17 fullyParallel: true, // Run tests in files in parallel
18 forbidOnly: !!process.env.CI, // Fail the build on CI if you accidentally left test.only in the source code.
19 retries: process.env.CI ? 2 : 0, // Retry on CI only
tests/config.yaml
+1 -1
@@ -4,7 +4,7 @@ allowed_referer: x.com
4 localhost_admin: false
5 vfs:
6 masks:
7 - "**/config.yaml*|test.ts|work":
7 + "**/config.yaml*|test.ts|work|*uploaded":
8 can_see: false
9 tests/page/!*.png:
10 mime: text/plain