@samitouri / QOSami-HFS / commits / 7003085d

frontend automatic tests

Massimo Melina committed Apr 8, 2025 at 14:51 UTC 7003085dc688f8d50d368d1fe83e5d8087032848
15 files changed +411 -16
.github/workflows/playwright.yml new
+27
@@ -0,0 +1,27 @@
1 +name: Playwright Tests
2 +on:
3 + push:
4 + branches: [ main ]
5 + pull_request:
6 + branches: [ main ]
7 +jobs:
8 + test:
9 + timeout-minutes: 60
10 + runs-on: ubuntu-latest
11 + steps:
12 + - uses: actions/checkout@v4
13 + - uses: actions/setup-node@v4
14 + with:
15 + node-version: lts/*
16 + - name: Install dependencies
17 + run: npm ci
18 + - name: Install Playwright Browsers
19 + run: npx playwright install --with-deps
20 + - name: Run Playwright tests
21 + run: npx playwright test
22 + - uses: actions/upload-artifact@v4
23 + if: ${{ !cancelled() }}
24 + with:
25 + name: playwright-report
26 + path: playwright-report/
27 + retention-days: 30
.gitignore
+8 -1
@@ -13,4 +13,11 @@ custom.html
13 accounts.yaml
14 .DS_Store
15 storage
16 -*.kv
\ No newline at end of file
16 +*.kv
17 +
18 +# Playwright
19 +/test-results/
20 +/playwright-report/
21 +/blob-report/
22 +/playwright/.cache/
23 +e2e/frontend.spec.ts-snapshots
\ No newline at end of file
admin/src/FileForm.ts
+1
@@ -362,6 +362,7 @@ function LinkField({ value, statusApi }: LinkFieldProps) {
362 !urls ? 'error' : // check data is ok
363 h(DisplayField, {
364 label: "Link",
365 + className: 'maskInTests',
366 value: link || `outside of configured main address (${baseHost})`,
367 error,
368 InputProps: link ? { inputComponent: RenderLink } : undefined,
admin/src/InternetPage.ts
+2 -2
@@ -307,7 +307,7 @@ export default function InternetPage({ setTitleSide }: PageProps) {
307 const direct = publicIps.includes(data?.localIp!)
308 return h(Flex, { justifyContent: 'space-around' },
309 h(Device, { name: "Server", icon: direct ? Storage : HomeWorkTwoTone, color: localColor, ip: data?.localIp,
310 - below: port && h(Box, { fontSize: 'smaller' }, "port ", port),
310 + below: port && h(Box, { fontSize: 'smaller', className: 'port' }, "port ", port),
311 }),
312 !direct && h(DataLine),
313 !direct && h(Device, {
@@ -462,7 +462,7 @@ function Device({ name, icon, color, ip, below }: any) {
462 return h(Box, { display: 'inline-block', textAlign: 'center' },
463 h(icon, { color, sx: { fontSize, mb: '-0.1em' } }),
464 h(Box, { fontSize: 'larger' }, name),
465 - h(Box, { fontSize: 'smaller', whiteSpace: 'pre-wrap' }, wantArray(ip).join('\n') || "unknown"),
465 + h(Box, { fontSize: 'smaller', whiteSpace: 'pre-wrap', className: 'ip' }, wantArray(ip).join('\n') || "unknown"),
466 below,
467 )
468 }
admin/src/MainMenu.ts
+1 -1
@@ -64,7 +64,7 @@ export default function Menu({ onSelect, itemTitle }: { onSelect: ()=>void, item
64 h(Box, { id: 'hfs-name', display: 'flex', px: 2, py: .5, gap: 2, alignItems: 'center' },
65 h('a', { href: WEBSITE, target: 'website', style: { textDecoration: 'none' } },
66 h(Box, { color: 'primary.contrastText', fontSize: 'min(3rem, max(5vw, 4vh))' }, 'HFS')),
67 - h(Box, { fontSize: 'small' }, replaceStringToReact(VERSION||'', /-/, () => h('br'))),
67 + h(Box, { fontSize: 'small', className: 'hideInTests' }, replaceStringToReact(VERSION||'', /-/, () => h('br'))),
68 short && h('img', { src: logo, style: { height: '2.5em' } }),
69 ),
70 mainMenu.map((it, idx) => hTooltip( itemTitle(idx), getMenuLabel(it) + ' ' + itemTitle(idx),
admin/src/VfsPage.ts
+2 -1
@@ -59,7 +59,8 @@ export default function VfsPage({ setTitleSide }: PageProps) {
59 severity: 'info',
60 children: [
61 "Your shared files can be browsed from ",
62 - reactJoin(" or ", urls.slice(0,3).map(href => h(Link, { href, target: 'frontend' }, href)))
62 + h('span', { className: 'hideInTests', key: 0 },
63 + reactJoin(" or ", urls.slice(0,3).map(href => h(Link, { href, target: 'frontend' }, href))) )
64 ]
65 }, [anythingShared, urls])
66
dev.md
+3 -4
@@ -30,10 +30,8 @@ been built, so their files are available in `dist` folder.
30
31 To run tests
32 - `npm run build-all`
33 -- `npm run server-for-test` and leave it running.
34 -- `npm test`
35 -
36 -Alternatively you can run a development server, just be sure to load config from `tests` folder.
33 +- `npm run test-with-server` (backend tests)
34 +- `npx playwright test` (these are UI tests)
35
36 # File organization
37
@@ -47,6 +45,7 @@ Additionally, you have the following folders:
45 - plugins: a collection of plugins that are pre-installed
46 - shared: code shared between Frontend and Admin
47 - tests: automated tests with related resources
48 +- e2e: automated UI tests (first execution will give an error because it's creating screenshots)
49
50 # Known problems
51 - vite's proxying server (but also CRA's) doesn't play nicely with SSE, leaving sockets open
e2e/frontend.spec.ts new
+263
@@ -0,0 +1,263 @@
1 +import { test, expect, Page } from '@playwright/test'
2 +import fs from 'fs'
3 +
4 +const username = 'rejetto'
5 +const password = 'password'
6 +
7 +const t = Date.UTC(2025, 0, 20, 3, 0, 0, 0) / 1000 // a fixed timestamp, for visual comparison
8 +fs.utimesSync('tests', t, t)
9 +fs.utimesSync('tests/config.yaml', t, t)
10 +
11 +// a generic test touch several parts
12 +test('around1', async ({ page }) => {
13 + await page.goto('http://localhost:81/');
14 + await expect(page).toHaveTitle(/File server/);
15 + await screenshot(page);
16 + await page.getByRole('button', { name: 'Login' }).click();
17 + await expect(page.getByRole('dialog', {})).toBeVisible();
18 + await screenshot(page);
19 + await page.getByRole('textbox', { name: 'Username' }).fill(username);
20 + await page.getByRole('textbox', { name: 'Username' }).press('Tab');
21 + await page.getByRole('textbox', { name: 'Password' }).fill(password);
22 + await page.getByRole('button', { name: 'Continue' }).click();
23 + await page.locator('div').filter({ hasText: 'Logged in' }).nth(3).click();
24 + await screenshot(page);
25 + await page.getByRole('button', { name: username }).click();
26 + await page.getByRole('button', { name: 'Logout' }).click();
27 + await page.getByText('Logged out').click();
28 + await page.getByRole('link', { name: 'cantListBut, Folder' }).click();
29 + await page.getByText('x!WarningForbidden').click();
30 + await page.getByRole('button', { name: 'Close' }).click();
31 + await page.getByRole('link', { name: 'cantListPage, Folder' }).click();
32 + await page.getByRole('link', { name: 'alfa.txt' }).click();
33 + await expect(page.getByRole('dialog')).toMatchAriaSnapshot(`
34 + - dialog:
35 + - button "Close"
36 + - heading "File menu" [level=1]
37 + - term: Name
38 + - definition: alfa.txt
39 + - term: Size
40 + - definition: 6 B
41 + - term: Timestamp
42 + - definition: /\\d+\\/\\d+\\/\\d+, \\d+:\\d+:\\d+/
43 + - term: Creation
44 + - definition: /\\d+\\/\\d+\\/\\d+, \\d+:\\d+:\\d+/
45 + - link "Download"
46 + - link "Open"
47 + `);
48 + await page.getByRole('link', { name: 'Download' }).click(); // this also closes the dialog
49 + await page.getByRole('link', { name: 'config.yaml', exact: true }).click();
50 + await screenshot(page);
51 + await page.getByRole('button', { name: 'Close' }).click();
52 + await page.getByRole('link', { name: 'cantListPage' }).click();
53 + await page.getByRole('button', { name: 'Calculate' }).click();
54 + await page.getByText('KB / 4 files').click();
55 + await page.locator('#menu-prop-name').getByText('cantListPage').click();
56 + await page.getByRole('link', { name: 'Download' }).click();
57 + await page.getByRole('link', { name: 'home' }).click();
58 + await page.getByRole('button', { name: 'Select' }).click();
59 + await page.getByRole('textbox', { name: 'Type here to filter the list' }).click();
60 + await page.getByRole('textbox', { name: 'Type here to filter the list' }).fill('x');
61 + await page.getByText('filtered').click();
62 + await screenshot(page);
63 + await page.getByRole('button', { name: 'Select' }).click();
64 + await page.getByText('file, 10 folders, 6 B').click();
65 + await page.getByRole('link', { name: 'cantListPageAlt, Folder' }).click();
66 + await page.getByRole('link', { name: 'home' }).click();
67 + await page.getByRole('link', { name: 'f1, Folder' }).click();
68 + await page.getByRole('link', { name: 'page, Folder' }).click();
69 + await page.getByRole('img', { name: 'gpl logo' }).click();
70 + await page.getByRole('heading', { name: 'This is a test' }).click();
71 + await page.goBack();
72 + await page.getByRole('link', { name: 'home' }).click();
73 +
74 + const isPhone = await page.evaluate(() => window.matchMedia("(max-width: 600px)").matches);
75 + if (isPhone)
76 + await page.getByRole('listitem').filter({ hasText: 'for-disabled' }).getByRole('button').click();
77 + else
78 + await page.getByRole('listitem').filter({ hasText: 'for-disabledMenu' }).getByRole('button').click();
79 + await expect(page.getByText('Missing permission')).toBeVisible();
80 + await page.getByRole('button', { name: 'Close' }).click();
81 + await page.getByRole('link', { name: 'cantSearchForMasks, Folder' }).click();
82 + await expect(page.getByRole('link', { name: 'cantSearchForMasks' })).toBeVisible();
83 + await page.getByRole('link', { name: 'cantSearchForMasks' }).click();
84 + await expect(page.locator('#menu-prop-name').getByText('cantSearchForMasks')).toBeVisible();
85 + await page.getByRole('button', { name: 'Close' }).click();
86 + await page.getByRole('link', { name: 'cantSearchForMasks' }).click();
87 + await page.locator('div').filter({ hasText: 'xFolder' }).nth(2).click();
88 + await page.getByRole('button', { name: 'Close' }).click();
89 + await page.getByRole('link', { name: 'home' }).click();
90 + await expect(page.getByText('file, 10 folders, 6 B')).toBeVisible();
91 +});
92 +
93 +test('search1', async ({ page }) => {
94 + await page.goto('http://localhost:81/');
95 + await page.getByRole('button', { name: 'Search' }).click();
96 + await page.locator('input[name="name"]').fill('a');
97 + await page.getByRole('button', { name: 'Continue' }).click();
98 + await page.getByText('files, 12 folders, 40.9 KB').click();
99 + await page.getByRole('link', { name: 'cantListPage/ alfa.txt' }).click();
100 + await page.getByRole('button', { name: 'Close' }).click();
101 + await page.getByRole('button', { name: 'Clear search' }).click();
102 +
103 + await page.getByRole('button', { name: 'Search' }).click();
104 + await page.locator('input[name="name"]').fill('a*');
105 + await page.locator('input[name="name"]').press('Enter');
106 + await page.getByText('files, 36 B').click();
107 +
108 + await page.getByRole('link', { name: 'home' }).click();
109 + await page.getByRole('button', { name: 'Close' }).click();
110 + await page.getByRole('link', { name: 'home' }).click();
111 + await page.getByRole('button', { name: 'Close' }).click();
112 + await page.getByRole('button', { name: 'Clear search' }).click();
113 + await page.getByRole('link', { name: 'home' }).click();
114 + await page.locator('div').filter({ hasText: 'xFolder' }).nth(2).click();
115 + await page.getByRole('button', { name: 'Close' }).click();
116 + await page.getByRole('button', { name: 'Options' }).click();
117 + await expect(page.locator('#option-sort-by')).toBeVisible();
118 + await expect(page.locator('#option-sort-by')).toBeVisible();
119 + await page.getByRole('dialog').locator('div').nth(2).click();
120 + await page.locator('#option-sort-by').selectOption('size');
121 + await page.getByRole('checkbox', { name: 'Invert order' }).check();
122 + await page.getByRole('slider').fill('6');
123 + await page.locator('#option-theme').selectOption('dark');
124 + await page.getByRole('button', { name: 'Close' }).click();
125 + await page.getByRole('link', { name: 'cantListPageAlt, Folder' }).click();
126 + await page.getByText('files, 29 KB').click();
127 + await page.mouse.click(1, 1); // avoid focus inconsistencies
128 + await screenshot(page);
129 + await expect(page.getByRole('list')).toMatchAriaSnapshot(`
130 + - list:
131 + - listitem:
132 + - link "test.ts"
133 + - text: /\\d+\\.\\d+ KB/
134 + - listitem:
135 + - link "config.yaml.bak"
136 + - text: 5.1 KB
137 + - listitem:
138 + - link "config.yaml"
139 + - text: 5.1 KB
140 + - listitem:
141 + - link "alfa.txt"
142 + - text: 6 B
143 + `);
144 +
145 + await page.getByRole('button', { name: 'Zip' }).click();
146 + await expect(page.getByRole('dialog')).toMatchAriaSnapshot(`
147 + - dialog:
148 + - button "Close"
149 + - heading "Confirm" [level=1]
150 + - paragraph: Download WHOLE folder as ZIP archive?
151 + - link "Yes":
152 + - button "Yes"
153 + - button "Don't"
154 + - button "Select some files"
155 + `);
156 + await page.getByRole('button', { name: 'Don\'t' }).click();
157 + await page.getByRole('button', { name: 'Zip' }).click();
158 + await page.getByRole('button', { name: 'Select some files' }).click();
159 + await page.getByText('Use checkboxes to select the').click();
160 + await page.getByRole('button', { name: 'Close' }).click();
161 + await page.getByRole('textbox', { name: 'Type here to filter the list' }).click();
162 +});
163 +
164 +test('frontend-admin', async ({ page }) => {
165 + await page.goto('http://localhost:81/');
166 + await page.getByRole('button', { name: 'Options' }).click();
167 + // no admin button yet,
168 + await expect(page.getByRole('dialog')).toMatchAriaSnapshot(`
169 + - dialog:
170 + - button "Close"
171 + - heading "Options" [level=1]
172 + - combobox:
173 + - 'option "Sort by: name" [selected]'
174 + - 'option "Sort by: extension"'
175 + - 'option "Sort by: size"'
176 + - 'option "Sort by: time"'
177 + - 'option "Sort by: creation"'
178 + - checkbox "Invert order"
179 + - text: Invert order
180 + - checkbox "Folders first" [checked]
181 + - text: Folders first
182 + - checkbox "Numeric names"
183 + - text: "Numeric names Tiles mode: off"
184 + - slider: "0"
185 + - combobox:
186 + - 'option "Theme: auto" [selected]'
187 + - 'option "Theme: light"'
188 + - 'option "Theme: dark"'
189 + `);
190 + await page.getByRole('button', { name: 'Close' }).click();
191 + await page.getByRole('button', { name: 'Login' }).click();
192 + await page.getByRole('textbox', { name: 'Username' }).fill(username);
193 + await page.getByRole('textbox', { name: 'Username' }).press('Tab');
194 + await page.getByRole('textbox', { name: 'Password' }).fill(password);
195 + await page.getByRole('textbox', { name: 'Password' }).press('Enter');
196 + await page.getByRole('button', { name: 'Options' }).click();
197 + const page1Promise = page.waitForEvent('popup');
198 + await page.getByRole('button', { name: 'Admin-panel' }).click();
199 + const page1 = await page1Promise;
200 + await expect(page1).toHaveTitle(/HFS Admin-panel/);
201 +})
202 +
203 +test('admin1', async ({ page }) => {
204 + await page.goto('http://localhost:81/~/admin/');
205 + await page.getByRole('textbox', { name: 'Username' }).fill(username);
206 + await page.getByRole('textbox', { name: 'Password' }).fill(password);
207 + await page.getByRole('textbox', { name: 'Password' }).press('Enter');
208 +
209 + const isPhone = await page.evaluate(() => window.matchMedia("(max-width: 600px)").matches);
210 + async function clickMenu(text: string) {
211 + if (isPhone)
212 + await page.getByRole('button', { name: 'menu' }).nth(0).click(); // on phones the menu is popup
213 + await page.getByRole('link', { name: text }).click();
214 + await page.waitForTimeout(100);
215 + }
216 + async function closePhoneDialog() { // on phone, some content is displayed in dialogs that need to be closed before having access to the outer content
217 + if (isPhone)
218 + await page.getByRole('button', { name: 'Close' }).click();
219 + }
220 +
221 + await clickMenu('Shared files')
222 + await screenshot(page)
223 + await clickMenu('Accounts');
224 + await screenshot(page)
225 + await page.getByText('rejetto(admins,').click();
226 + await screenshot(page)
227 + await closePhoneDialog();
228 + await clickMenu('Options');
229 + await expect(page.getByText('Correctly working on port')).toBeVisible(); // wait for loading to be done
230 + await page.mouse.click(1, 1); // avoid focus inconsistencies
231 + await screenshot(page)
232 +
233 + await clickMenu('Internet');
234 + await expect(page.getByRole('button', { name: 'Verify' })).toBeVisible(); // first data is slow on this page, be sure to wait
235 + await page.mouse.click(1, 1); // avoid focus inconsistencies
236 + await screenshot(page, '.ip,.port')
237 + await clickMenu('Logs');
238 + await screenshot(page, '.MuiDataGrid-virtualScrollerRenderZone');
239 + await page.getByRole('tab').nth(2).click();
240 + await page.getByRole('tab').nth(3).click();
241 + await page.getByRole('tab').nth(4).click();
242 + await page.getByRole('button', { name: '(Options)' }).click();
243 + await page.locator('div').filter({ hasText: 'ServedRequests are logged hereNot servedIf you want errors in a different' }).nth(3).click();
244 + await page.getByRole('button', { name: '(Close)' }).click();
245 + await expect(page.getByText('LogsServedNot')).toBeVisible();
246 + await clickMenu('Language');
247 + await screenshot(page, '.MuiDataGrid-virtualScrollerRenderZone');
248 + await clickMenu('Plugins');
249 + await screenshot(page);
250 + await page.getByRole('tab', { name: 'Search' }).click();
251 + await page.getByRole('tab', { name: 'updates' }).click();
252 + await clickMenu('Custom HTML');
253 + await screenshot(page);
254 + await page.getByRole('main').click();
255 + await clickMenu('Logout');
256 + await screenshot(page);
257 +});
258 +
259 +function screenshot(page: Page, selectorForMask='') {
260 + if (selectorForMask)
261 + selectorForMask = ',' + selectorForMask
262 + return expect(page).toHaveScreenshot({ fullPage: true, mask: [page.locator(`.maskInTests${selectorForMask}`)] });
263 +}
\ No newline at end of file
e2e/screenshot.css new
+6
@@ -0,0 +1,6 @@
1 +/* avoid meaningless graphical effects that may cause changes in the screenshots */
2 +*:focus { outline: none !important; box-shadow: none !important; }
3 +main *:hover { background: none !important; }
4 +
5 +.hideInTests, /* use for elements with variable size, where masking would produce changes anyway */
6 +.list-wrapper:not([uri="/"]) .entry-ts { display: none } /* ignore changing timestamps inside folders */
\ No newline at end of file
e2e/tsconfig.json new
+4
@@ -0,0 +1,4 @@
1 +{
2 + "extends": "../tsconfig-web",
3 + "rootDir": "."
4 +}
\ No newline at end of file
frontend/src/BrowseFiles.ts
+1
@@ -49,6 +49,7 @@ export function BrowseFiles() {
49 return h(CustomCode, { name: 'unauthorized' }, h('h1', { className: 'unauthorized' }, t`Unauthorized`) )
50 return h('div', propsDropFiles, // element dedicated to drop-files to cover full screen
51 h('div', {
52 + uri: path, // used by UI tests
53 className: 'list-wrapper ' + (tile_size ? 'tiles-mode' : 'list-mode'),
54 style: { '--tile-size': tile_size },
55 },
package.json
+4 -2
@@ -16,12 +16,13 @@
16 "build-server": "rm -rf dist/src dist/plugins && tsc --target es2018 && touch package.json && cp -v -r package.json central.json README* LICENSE* hfs.ico plugins dist && find dist -name .DS_Store -o -name storage -exec rm -rf {} + && node afterbuild.js",
17 "build-frontend": "npm run build --workspace=frontend",
18 "build-admin": "npm run build --workspace=admin",
19 - "server-for-test": "node dist/src --cwd . --config tests && rm custom.html",
19 + "server-for-test": "node dist/src --cwd . --config tests --debug && rm custom.html",
20 "server-for-test-dev": "cross-env DEV=1 FRONTEND_PROXY=3005 ADMIN_PROXY=3006 nodemon --ignore tests/ --watch src -e ts,tsx --exec ts-node src -- --cwd . --config tests",
21 "test": "mocha -r ts-node/register 'tests/**/*.ts'",
22 "test-with-server": "node dist/src --cwd . --config tests & pid=$! && mocha -r ts-node/register 'tests/**/*.ts'; mocha_exit=$?; kill $pid; exit $mocha_exit",
23 + "test-ui": "npx playwright test --ui",
24 "pub": "cd dist && npm publish",
24 - "dist": "npm run build-all && npm run dist-bin",
25 + "dist": "npm run build-all && npx playwright test && 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-*",
@@ -99,6 +100,7 @@
100 "yaml": "^2.0.0-10"
101 },
102 "devDependencies": {
103 + "@playwright/test": "^1.51.1",
104 "@types/archiver": "^5.1.1",
105 "@types/basic-auth": "^1.1.3",
106 "@types/formidable": "^3.4.1",
playwright.config.ts new
+87
@@ -0,0 +1,87 @@
1 +import { defineConfig, devices } from '@playwright/test';
2 +
3 +/**
4 + * Read environment variables from file.
5 + * https://github.com/motdotla/dotenv
6 + */
7 +// import dotenv from 'dotenv';
8 +// import path from 'path';
9 +// dotenv.config({ path: path.resolve(__dirname, '.env') });
10 +
11 +/**
12 + * See https://playwright.dev/docs/test-configuration.
13 + */
14 +export default defineConfig({
15 + testDir: './e2e',
16 + timeout: 15_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
20 + workers: process.env.CI ? 1 : undefined, // Opt out of parallel tests on CI.
21 + reporter: 'html', // Reporter to use. See https://playwright.dev/docs/test-reporters
22 + use: { // Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions.
23 + /* Base URL to use in actions like `await page.goto('/')`. */
24 + // baseURL: 'http://127.0.0.1:3000',
25 +
26 + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
27 + trace: 'on-first-retry',
28 + },
29 +
30 + /* Configure projects for major browsers */
31 + projects: [
32 + {
33 + name: 'chromium',
34 + use: { ...devices['Desktop Chrome'] },
35 + },
36 + {
37 + name: 'Android',
38 + use: { ...devices['Pixel 7'] },
39 + },
40 + {
41 + name: 'iPhone SE',
42 + use: { ...devices['iPhone SE'] },
43 + },
44 +/*
45 + {
46 + name: 'firefox',
47 + use: { ...devices['Desktop Firefox'] },
48 + },
49 +
50 + {
51 + name: 'webkit',
52 + use: { ...devices['Desktop Safari'] },
53 + },*/
54 +
55 + /* Test against mobile viewports. */
56 + // {
57 + // name: 'Mobile Chrome',
58 + // use: { ...devices['Pixel 5'] },
59 + // },
60 + // {
61 + // name: 'Mobile Safari',
62 + // use: { ...devices['iPhone 12'] },
63 + // },
64 +
65 + /* Test against branded browsers. */
66 + // {
67 + // name: 'Microsoft Edge',
68 + // use: { ...devices['Desktop Edge'], channel: 'msedge' },
69 + // },
70 + // {
71 + // name: 'Google Chrome',
72 + // use: { ...devices['Desktop Chrome'], channel: 'chrome' },
73 + // },
74 + ],
75 +
76 + expect: {
77 + toHaveScreenshot: {
78 + stylePath: 'e2e/screenshot.css',
79 + },
80 + },
81 + /* Run your local dev server before starting the tests */
82 + webServer: {
83 + command: 'npm run server-for-test',
84 + url: 'http://127.0.0.1:81',
85 + reuseExistingServer: !process.env.CI,
86 + },
87 +});
tsconfig-web.json
+1 -4
@@ -19,8 +19,5 @@
19 "isolatedModules": true,
20 "noEmit": true,
21 "jsx": "react-jsx"
22 - },
23 - "include": [
24 - "src"
25 - ]
22 + }
23 }
tsconfig.json
+1 -1
@@ -1,5 +1,5 @@
1 {
2 - "exclude": ["frontend","admin","tests","dist","shared","mui-grid-form"],
2 + "exclude": ["frontend","admin","tests","dist","shared","mui-grid-form","e2e","./playwright.config.ts"],
3 "compilerOptions": {
4 /* Visit https://aka.ms/tsconfig.json to read more about this file */
5