Skip to content

Commit c6664ed

Browse files
test(interface): add e2e tests with playwright (#720)
1 parent 32be4db commit c6664ed

10 files changed

Lines changed: 465 additions & 4 deletions

File tree

.github/workflows/nodejs.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,14 @@ jobs:
3030
- name: Install dependencies
3131
run: npm install --ignore-scripts
3232
- name: Build
33-
run: npm run build --ws --if-present
33+
run: npm run build
34+
- name: Install Playwright browsers
35+
if: matrix.os == 'ubuntu-latest'
36+
run: npx playwright install --with-deps
3437
- name: Run tests
3538
run: npm run coverage
39+
- name: Run e2e tests
40+
if: matrix.os == 'ubuntu-latest'
41+
run: npm run test:e2e
3642
- name: Send coverage report to Codecov
3743
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ typings/
6464
.next
6565

6666
nsecure-result.json
67+
!**/fixtures/nsecure-result.json
6768
vuln.json
6869
tmp/
6970
dist/
@@ -73,3 +74,6 @@ reports
7374
.DS_Store
7475

7576
.claude
77+
78+
# playwright test results
79+
test-results

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"build:workspaces": "npm run build --ws --if-present",
2323
"test": "npm run test:cli && npm run lint && npm run lint:css",
2424
"test:cli": "node --no-warnings --test \"test/**/*.test.js\"",
25+
"test:e2e": "playwright test",
2526
"test:all": "npm run test --ws --if-present",
2627
"coverage": "c8 --reporter=lcov npm run test",
2728
"ci:publish": "changeset publish",
@@ -79,6 +80,7 @@
7980
"@openally/config.eslint": "^2.4.2",
8081
"@openally/config.typescript": "1.3.0",
8182
"@openally/httpie": "1.1.2",
83+
"@playwright/test": "^1.50.0",
8284
"@stylistic/stylelint-plugin": "5.0.1",
8385
"@types/node": "25.5.0",
8486
"c8": "11.0.0",

playwright.config.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Import Third-party Dependencies
2+
import { defineConfig } from "@playwright/test";
3+
4+
export default defineConfig({
5+
testDir: "./test/e2e",
6+
use: {
7+
baseURL: "http://localhost:3000"
8+
},
9+
webServer: {
10+
command: "node . open ./test/e2e/fixtures/nsecure-result.json --port 3000 --ws-port 1339",
11+
env: { NODESECURE_NO_OPEN: true },
12+
port: 3000,
13+
timeout: 15_000
14+
}
15+
});

src/commands/http.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ export async function start(
7171
const link = `http://localhost:${httpServer.address().port}`;
7272
console.log(kleur.magenta().bold(await i18n.getToken("cli.http_server_started")), kleur.cyan().bold(link));
7373

74-
open(link);
74+
if (Boolean(process.env.NODESECURE_NO_OPEN) === false) {
75+
open(link);
76+
}
7577
});
7678

7779
new WebSocketServerInstanciator({
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
{
2+
"id": "j6vlz7",
3+
"rootDependency": {
4+
"name": "ms",
5+
"version": "2.1.3",
6+
"integrity": "sha512-EY5JVSmS/ZEMr9kLaphuJfQMLLbK87KibiJPcp3GB4YQHlFK6Ynk9mr3WRVyYqKw8akoxIoZfzLWnBTREgvbmw=="
7+
},
8+
"scannerVersion": "10.8.0",
9+
"vulnerabilityStrategy": "none",
10+
"warnings": [],
11+
"highlighted": {
12+
"contacts": [],
13+
"packages": [],
14+
"identifiers": []
15+
},
16+
"dependencies": {
17+
"ms": {
18+
"versions": {
19+
"2.1.3": {
20+
"id": 0,
21+
"type": "cjs",
22+
"usedBy": {},
23+
"isDevDependency": false,
24+
"existOnRemoteRegistry": true,
25+
"flags": [
26+
"hasWarnings"
27+
],
28+
"warnings": [
29+
{
30+
"kind": "unsafe-regex",
31+
"location": [
32+
[
33+
53,
34+
14
35+
],
36+
[
37+
53,
38+
144
39+
]
40+
],
41+
"source": "JS-X-Ray",
42+
"i18n": "sast_warnings.unsafe_regex",
43+
"severity": "Warning",
44+
"experimental": false,
45+
"value": "^(-?(?:\\d+)?\\.?\\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$",
46+
"file": "index.js"
47+
}
48+
],
49+
"dependencyCount": 0,
50+
"gitUrl": null,
51+
"alias": {},
52+
"description": "Tiny millisecond conversion utility",
53+
"size": 6913,
54+
"author": null,
55+
"scripts": {
56+
"precommit": "lint-staged",
57+
"lint": "eslint lib/* bin/*",
58+
"test": "mocha tests.js"
59+
},
60+
"licenses": [
61+
{
62+
"licenses": {
63+
"MIT": "https://spdx.org/licenses/MIT.html#licenseText"
64+
},
65+
"spdx": {
66+
"osi": true,
67+
"fsf": true,
68+
"fsfAndOsi": true,
69+
"includesDeprecated": false
70+
},
71+
"fileName": "package.json"
72+
},
73+
{
74+
"licenses": {
75+
"MIT": "https://spdx.org/licenses/MIT.html#licenseText"
76+
},
77+
"spdx": {
78+
"osi": true,
79+
"fsf": true,
80+
"fsfAndOsi": true,
81+
"includesDeprecated": false
82+
},
83+
"fileName": "license.md"
84+
}
85+
],
86+
"uniqueLicenseIds": [
87+
"MIT"
88+
],
89+
"composition": {
90+
"extensions": [
91+
".md",
92+
".js",
93+
".json"
94+
],
95+
"files": [
96+
"index.js",
97+
"license.md",
98+
"package.json",
99+
"readme.md"
100+
],
101+
"minified": [],
102+
"unused": [],
103+
"missing": [],
104+
"required_files": [],
105+
"required_nodejs": [],
106+
"required_thirdparty": [],
107+
"required_subpath": {}
108+
},
109+
"repository": "vercel/ms",
110+
"integrity": "1f743a8b72bd7a02b88d452246f50ff14164f32e",
111+
"links": {
112+
"npm": "https://www.npmjs.com/package/ms/v/2.1.3",
113+
"homepage": "https://github.com/vercel/ms#readme",
114+
"repository": "https://github.com/vercel/ms"
115+
}
116+
}
117+
},
118+
"vulnerabilities": [],
119+
"metadata": {
120+
"homepage": "https://github.com/vercel/ms#readme",
121+
"publishedCount": 32,
122+
"lastVersion": "2.1.3",
123+
"lastUpdateAt": "2020-12-08T13:54:35.223Z",
124+
"hasReceivedUpdateInOneYear": false,
125+
"hasChangedAuthor": false,
126+
"integrity": {
127+
"2.1.3": "1f743a8b72bd7a02b88d452246f50ff14164f32e"
128+
},
129+
"author": {
130+
"name": "rauchg",
131+
"email": "rauchg@gmail.com"
132+
},
133+
"publishers": [
134+
{
135+
"name": "vercel-release-bot",
136+
"email": "infra+release@vercel.com",
137+
"version": "4.0.0-nightly.202508271359",
138+
"at": "2025-08-27T14:00:01.232Z"
139+
},
140+
{
141+
"name": "leerobinson",
142+
"email": "lrobinson2011@gmail.com",
143+
"version": "3.0.0-canary.1",
144+
"at": "2021-09-15T15:40:43.956Z"
145+
},
146+
{
147+
"name": "mrmckeb",
148+
"email": "mrmckeb.npm@outlook.com",
149+
"version": "3.0.0-beta.2",
150+
"at": "2021-08-25T16:55:32.842Z"
151+
},
152+
{
153+
"name": "styfle",
154+
"email": "steven@ceriously.com",
155+
"version": "2.1.3",
156+
"at": "2020-12-08T13:54:35.223Z"
157+
},
158+
{
159+
"name": "leo",
160+
"email": "leo@zeit.co",
161+
"version": "2.1.1",
162+
"at": "2017-11-30T18:30:16.876Z"
163+
},
164+
{
165+
"name": "rauchg",
166+
"email": "rauchg@gmail.com",
167+
"version": "0.7.1",
168+
"at": "2015-04-20T23:38:57.957Z"
169+
}
170+
],
171+
"maintainers": [
172+
{
173+
"email": "matheus.frndes@gmail.com",
174+
"name": "matheuss"
175+
},
176+
{
177+
"email": "rauchg@gmail.com",
178+
"name": "rauchg"
179+
},
180+
{
181+
"email": "nick.tracey@vercel.com",
182+
"name": "nick.tracey"
183+
},
184+
{
185+
"email": "infra+release@vercel.com",
186+
"name": "vercel-release-bot"
187+
},
188+
{
189+
"email": "team@zeit.co",
190+
"name": "zeit-bot"
191+
},
192+
{
193+
"email": "matt.j.straka@gmail.com",
194+
"name": "matt.straka"
195+
}
196+
],
197+
"hasManyPublishers": false
198+
}
199+
}
200+
},
201+
"metadata": {
202+
"startedAt": 1774800760807,
203+
"executionTime": 1250,
204+
"apiCalls": [
205+
{
206+
"name": "pacote.manifest ms@2.1.3",
207+
"startedAt": 1774800760809,
208+
"executionTime": 7
209+
},
210+
{
211+
"name": "pacote.extract ms@2.1.3",
212+
"startedAt": 1774800760821,
213+
"executionTime": 114
214+
},
215+
{
216+
"name": "tarball.scanDirOrArchive ms@2.1.3",
217+
"startedAt": 1774800760938,
218+
"executionTime": 330
219+
}
220+
],
221+
"apiCallsCount": 3,
222+
"errorCount": 0,
223+
"errors": []
224+
}
225+
}

test/e2e/navigation.spec.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Import Third-party Dependencies
2+
import { test, expect } from "@playwright/test";
3+
4+
// CONSTANTS
5+
const kDataMenus = [
6+
"home--view",
7+
"network--view",
8+
"tree--view",
9+
"warnings--view"
10+
];
11+
const kAlwaysVisibleMenus = [
12+
"search--view",
13+
"settings--view"
14+
];
15+
16+
test.describe("[navigation] with data", () => {
17+
test.beforeEach(async({ page }) => {
18+
await page.goto("/");
19+
await page.waitForSelector(`[data-menu="network--view"].active`);
20+
});
21+
22+
test("all tabs are visible in the sidebar", async({ page }) => {
23+
for (const menu of [...kDataMenus, ...kAlwaysVisibleMenus]) {
24+
await expect(page.locator(`[data-menu="${menu}"]`)).not.toContainClass("hidden");
25+
}
26+
});
27+
28+
test("network view is active by default", async({ page }) => {
29+
await expect(page.locator("#network--view")).not.toContainClass("hidden");
30+
await expect(page.locator(`[data-menu="network--view"]`)).toContainClass("active");
31+
});
32+
33+
test("clicking the settings tab shows the settings view", async({ page }) => {
34+
await page.locator(`[data-menu="settings--view"]`).click();
35+
36+
await expect(page.locator("#settings--view")).not.toContainClass("hidden");
37+
await expect(page.locator("#network--view")).toContainClass("hidden");
38+
});
39+
40+
test("pressing S navigates to the settings view", async({ page }) => {
41+
await page.keyboard.press("s");
42+
43+
await expect(page.locator("#settings--view")).not.toContainClass("hidden");
44+
});
45+
46+
test("pressing A navigates to the warnings view", async({ page }) => {
47+
await page.keyboard.press("a");
48+
49+
await expect(page.locator("#warnings--view")).not.toContainClass("hidden");
50+
});
51+
});
52+
53+
test.describe("[navigation] without data", () => {
54+
test.beforeEach(async({ page }) => {
55+
await page.route("**/data", (route) => route.fulfill({ status: 204 }));
56+
await page.goto("/");
57+
await page.waitForSelector(`[data-menu="search--view"].active`);
58+
});
59+
60+
test("data-dependent tabs are hidden in the sidebar", async({ page }) => {
61+
for (const menu of kDataMenus) {
62+
await expect(page.locator(`[data-menu="${menu}"]`)).toContainClass("hidden");
63+
}
64+
});
65+
66+
test("always visible menus tabs remain visible", async({ page }) => {
67+
for (const menu of kAlwaysVisibleMenus) {
68+
await expect(page.locator(`[data-menu="${menu}"]`)).not.toContainClass("hidden");
69+
}
70+
});
71+
72+
test("search view is the active view", async({ page }) => {
73+
await expect(page.locator("#search--view")).not.toContainClass("hidden");
74+
await expect(page.locator(`[data-menu="search--view"]`)).toContainClass("active");
75+
});
76+
});

0 commit comments

Comments
 (0)