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