@networkpro/web 1.25.9 → 1.25.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.template +1 -1
- package/.github/workflows/auto-assign.yml +2 -0
- package/.github/workflows/backup-branch.yml +2 -0
- package/.github/workflows/build-and-publish.yml +0 -15
- package/.github/workflows/check-security-txt-expiry.yml +2 -0
- package/.github/workflows/dependency-review.yml +2 -0
- package/.github/workflows/meta-check.yml +3 -0
- package/.github/workflows/prevent-audit-merge.yml +3 -0
- package/.github/workflows/secret-scan.yml +2 -0
- package/.github/workflows/templates/check-codeql.template.yml +2 -0
- package/.github/workflows/templates/publish.template.yml +0 -15
- package/CHANGELOG.md +52 -1
- package/cspell.json +3 -0
- package/package.json +6 -4
- package/src/lib/security/probely.js +13 -4
- package/tests/e2e/app.spec.js +17 -29
- package/tests/e2e/mobile.spec.js +17 -39
- package/tests/e2e/shared/helpers.js +52 -32
package/.env.template
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# Rename to `.env` (or `.env.local`) and customize as needed
|
|
6
6
|
|
|
7
7
|
# Custom environment mode for scripts and tooling
|
|
8
|
-
# One of: dev, test, ci,
|
|
8
|
+
# One of: dev, test, ci, audit, production
|
|
9
9
|
ENV_MODE=dev
|
|
10
10
|
|
|
11
11
|
# Optional: API keys or tokens for local dev (never commit real values)
|
|
@@ -42,11 +42,6 @@ jobs:
|
|
|
42
42
|
cache: npm
|
|
43
43
|
cache-dependency-path: package-lock.json
|
|
44
44
|
|
|
45
|
-
#- name: Show Node.js and npm versions
|
|
46
|
-
# run: |
|
|
47
|
-
# echo "Node.js version: $(node -v)"
|
|
48
|
-
# echo "npm version: $(npm -v)"
|
|
49
|
-
|
|
50
45
|
- name: Upgrade npm
|
|
51
46
|
run: |
|
|
52
47
|
corepack enable
|
|
@@ -132,11 +127,6 @@ jobs:
|
|
|
132
127
|
cache: npm
|
|
133
128
|
cache-dependency-path: package-lock.json
|
|
134
129
|
|
|
135
|
-
#- name: Show Node.js and npm versions
|
|
136
|
-
# run: |
|
|
137
|
-
# echo "Node.js version: $(node -v)"
|
|
138
|
-
# echo "npm version: $(npm -v)"
|
|
139
|
-
|
|
140
130
|
- name: Upgrade npm
|
|
141
131
|
run: |
|
|
142
132
|
corepack enable
|
|
@@ -193,11 +183,6 @@ jobs:
|
|
|
193
183
|
cache: npm
|
|
194
184
|
cache-dependency-path: package-lock.json
|
|
195
185
|
|
|
196
|
-
#- name: Show Node.js and npm versions
|
|
197
|
-
# run: |
|
|
198
|
-
# echo "Node.js version: $(node -v)"
|
|
199
|
-
# echo "npm version: $(npm -v)"
|
|
200
|
-
|
|
201
186
|
- name: Upgrade npm
|
|
202
187
|
run: |
|
|
203
188
|
corepack enable
|
|
@@ -50,11 +50,6 @@ jobs:
|
|
|
50
50
|
cache: npm
|
|
51
51
|
cache-dependency-path: package-lock.json
|
|
52
52
|
|
|
53
|
-
#- name: Show Node.js and npm versions
|
|
54
|
-
# run: |
|
|
55
|
-
# echo "Node.js version: $(node -v)"
|
|
56
|
-
# echo "npm version: $(npm -v)"
|
|
57
|
-
|
|
58
53
|
- name: Upgrade npm
|
|
59
54
|
run: |
|
|
60
55
|
corepack enable
|
|
@@ -136,11 +131,6 @@ jobs:
|
|
|
136
131
|
cache: npm
|
|
137
132
|
cache-dependency-path: package-lock.json
|
|
138
133
|
|
|
139
|
-
#- name: Show Node.js and npm versions
|
|
140
|
-
# run: |
|
|
141
|
-
# echo "Node.js version: $(node -v)"
|
|
142
|
-
# echo "npm version: $(npm -v)"
|
|
143
|
-
|
|
144
134
|
- name: Upgrade npm
|
|
145
135
|
run: |
|
|
146
136
|
corepack enable
|
|
@@ -197,11 +187,6 @@ jobs:
|
|
|
197
187
|
cache: npm
|
|
198
188
|
cache-dependency-path: package-lock.json
|
|
199
189
|
|
|
200
|
-
#- name: Show Node.js and npm versions
|
|
201
|
-
# run: |
|
|
202
|
-
# echo "Node.js version: $(node -v)"
|
|
203
|
-
# echo "npm version: $(npm -v)"
|
|
204
|
-
|
|
205
190
|
- name: Upgrade npm
|
|
206
191
|
run: |
|
|
207
192
|
corepack enable
|
package/CHANGELOG.md
CHANGED
|
@@ -22,6 +22,55 @@ This project attempts to follow [Keep a Changelog](https://keepachangelog.com/en
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
## [1.25.11] - 2025-11-12
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- `gotoDesktop(page, path)` and `gotoMobile(page, path)` helper functions to streamline viewport + navigation setup.
|
|
30
|
+
- `clickAndWaitForNavigation(page, locator, options)` utility for safe SPA or full-page navigation detection with optional URL pattern matching.
|
|
31
|
+
- `DEBUG_LOGS` flag in `helpers.js` to allow toggling of console logs for test diagnostics.
|
|
32
|
+
- Navigation debug logs to `getVisibleNav()` to indicate which navigation region was detected (when debugging is enabled).
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- Refactored all E2E tests to use `gotoDesktop()` and `gotoMobile()` for consistency and DRY principles.
|
|
37
|
+
- Replaced brittle direct `waitForNavigation()` usages with `clickAndWaitForNavigation()` helper.
|
|
38
|
+
- Updated mobile and desktop tests to improve consistency across specs and improve visibility assertions.
|
|
39
|
+
|
|
40
|
+
### Removed
|
|
41
|
+
|
|
42
|
+
- Legacy direct `setViewportSize()` and `page.goto()` calls from individual test blocks (now handled via `goto*()` helpers).
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## [1.25.10] - 2025-11-12
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- Updated GitHub workflows to specify `ENV: ci` where appropriate:
|
|
51
|
+
- `templates/check-codeql.template.yml`
|
|
52
|
+
- `templates/publish.template.yml`
|
|
53
|
+
- `auto-assign.yml`
|
|
54
|
+
- `branch-backup.yml`
|
|
55
|
+
- `check-security-txt-expiry.yml`
|
|
56
|
+
- `dependency-review.yml`
|
|
57
|
+
- `meta-check.yml`
|
|
58
|
+
- `prevent-audit-merges.yml`
|
|
59
|
+
- `secret-scan.yml`
|
|
60
|
+
- Added `@sveltejs/adapter-netlify` devDependency for smoother toggling between production and audit modes.
|
|
61
|
+
- Production uses `@sveltejs/adapter-vercel` only. `@sveltejs/adapter-netlify` exists solely to support the audit environment.
|
|
62
|
+
- Bumped project version to `v1.25.10`.
|
|
63
|
+
- Updated dependencies:
|
|
64
|
+
- `@testing-library/svelte` `^5.2.8` → `^5.2.9`
|
|
65
|
+
- `eslint-plugin-jsdoc` `^61.1.12` → `^61.2.0`
|
|
66
|
+
- `posthog-js` `^1.290.0` → `^1.292.0`
|
|
67
|
+
|
|
68
|
+
## Removed
|
|
69
|
+
|
|
70
|
+
- Removed unneeded comments in `build-and-publish.yml` workflow.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
25
74
|
## [1.25.9] - 2025-11-11
|
|
26
75
|
|
|
27
76
|
### Changed
|
|
@@ -1827,7 +1876,9 @@ This enables analytics filtering and CSP hardening for the audit environment.
|
|
|
1827
1876
|
|
|
1828
1877
|
<!-- Link references -->
|
|
1829
1878
|
|
|
1830
|
-
[Unreleased]: https://github.com/netwk-pro/netwk-pro.github.io/compare/v1.25.
|
|
1879
|
+
[Unreleased]: https://github.com/netwk-pro/netwk-pro.github.io/compare/v1.25.11...HEAD
|
|
1880
|
+
[1.25.11]: https://github.com/netwk-pro/netwk-pro.github.io/releases/tag/v1.25.11
|
|
1881
|
+
[1.25.10]: https://github.com/netwk-pro/netwk-pro.github.io/releases/tag/v1.25.10
|
|
1831
1882
|
[1.25.9]: https://github.com/netwk-pro/netwk-pro.github.io/releases/tag/v1.25.9
|
|
1832
1883
|
[1.25.8]: https://github.com/netwk-pro/netwk-pro.github.io/releases/tag/v1.25.8
|
|
1833
1884
|
[1.25.7]: https://github.com/netwk-pro/netwk-pro.github.io/releases/tag/v1.25.7
|
package/cspell.json
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"CCPA",
|
|
15
15
|
"CPRA",
|
|
16
16
|
"cryptomator",
|
|
17
|
+
"domcontentloaded",
|
|
17
18
|
"dont",
|
|
18
19
|
"elif",
|
|
19
20
|
"Embedder",
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
"homescreen",
|
|
29
30
|
"HREFTOP",
|
|
30
31
|
"HSTS",
|
|
32
|
+
"interactable",
|
|
31
33
|
"Izzy",
|
|
32
34
|
"Keybase",
|
|
33
35
|
"keypair",
|
|
@@ -40,6 +42,7 @@
|
|
|
40
42
|
"lightningcss",
|
|
41
43
|
"linksheet",
|
|
42
44
|
"linktree",
|
|
45
|
+
"LOCALAPPDATA",
|
|
43
46
|
"lsheet",
|
|
44
47
|
"Mailvelope",
|
|
45
48
|
"Maricopa",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@networkpro/web",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.25.
|
|
4
|
+
"version": "1.25.11",
|
|
5
5
|
"description": "Locking Down Networks, Unlocking Confidence™ | Security, Networking, Privacy — Network Pro Strategies",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"advisory",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"verify": "npm run checkout",
|
|
55
55
|
"delete": "rm -rf .svelte-kit node_modules package-lock.json",
|
|
56
56
|
"clean": "npm run delete && npm cache clean --force && npm install",
|
|
57
|
+
"repair": "npm run delete && npx playwright uninstall && rm -rf %LOCALAPPDATA%/ms-playwright && npm install && npx playwright install",
|
|
57
58
|
"upgrade": "ncu -u --format group --color",
|
|
58
59
|
"check:updates": "ncu --format group --color",
|
|
59
60
|
"test": "npm run test:all",
|
|
@@ -85,7 +86,7 @@
|
|
|
85
86
|
},
|
|
86
87
|
"dependencies": {
|
|
87
88
|
"dompurify": "^3.3.0",
|
|
88
|
-
"posthog-js": "^1.
|
|
89
|
+
"posthog-js": "^1.292.0",
|
|
89
90
|
"semver": "^7.7.3",
|
|
90
91
|
"svelte": "5.43.6"
|
|
91
92
|
},
|
|
@@ -94,17 +95,18 @@
|
|
|
94
95
|
"@eslint/js": "^9.39.1",
|
|
95
96
|
"@lhci/cli": "^0.15.1",
|
|
96
97
|
"@playwright/test": "^1.56.1",
|
|
98
|
+
"@sveltejs/adapter-netlify": "^5.2.4",
|
|
97
99
|
"@sveltejs/adapter-vercel": "^6.1.1",
|
|
98
100
|
"@sveltejs/kit": "2.48.4",
|
|
99
101
|
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
|
100
102
|
"@testing-library/jest-dom": "^6.9.1",
|
|
101
|
-
"@testing-library/svelte": "^5.2.
|
|
103
|
+
"@testing-library/svelte": "^5.2.9",
|
|
102
104
|
"@vitest/coverage-v8": "3.2.4",
|
|
103
105
|
"autoprefixer": "^10.4.22",
|
|
104
106
|
"browserslist": "^4.28.0",
|
|
105
107
|
"eslint": "^9.39.1",
|
|
106
108
|
"eslint-config-prettier": "^10.1.8",
|
|
107
|
-
"eslint-plugin-jsdoc": "^61.
|
|
109
|
+
"eslint-plugin-jsdoc": "^61.2.0",
|
|
108
110
|
"eslint-plugin-svelte": "^3.13.0",
|
|
109
111
|
"globals": "^16.5.0",
|
|
110
112
|
"jsdom": "26.1.0",
|
|
@@ -15,7 +15,7 @@ This file is part of Network Pro.
|
|
|
15
15
|
* @updated 2025-11-11
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
/** @typedef {{ ua
|
|
18
|
+
/** @typedef {{ ua?: string, ip?: string }} ScannerInput */
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Check if a request is likely from Probely.
|
|
@@ -23,7 +23,13 @@ This file is part of Network Pro.
|
|
|
23
23
|
* @returns {boolean} - True if the request matches Probely’s fingerprint.
|
|
24
24
|
*/
|
|
25
25
|
export function isProbelyScanner({ ua, ip }) {
|
|
26
|
-
const
|
|
26
|
+
const PROBELY_UA_FRAGMENTS = [
|
|
27
|
+
'probelyspdr/',
|
|
28
|
+
'probelyfp/',
|
|
29
|
+
'probelymrkt/',
|
|
30
|
+
'probelysc/',
|
|
31
|
+
'python-httpx/',
|
|
32
|
+
];
|
|
27
33
|
const PROBELY_IPS = [
|
|
28
34
|
'18.235.241.170',
|
|
29
35
|
'52.65.214.19',
|
|
@@ -41,7 +47,10 @@ export function isProbelyScanner({ ua, ip }) {
|
|
|
41
47
|
const normalizedIP = ip?.trim() ?? '';
|
|
42
48
|
|
|
43
49
|
return (
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
PROBELY_UA_FRAGMENTS.some((fragment) =>
|
|
51
|
+
normalizedUA.includes(fragment.toLowerCase()),
|
|
52
|
+
) || PROBELY_IPS.includes(normalizedIP)
|
|
46
53
|
);
|
|
47
54
|
}
|
|
55
|
+
|
|
56
|
+
// cspell:ignore probelyfp probelymrkt probelysc httpx
|
package/tests/e2e/app.spec.js
CHANGED
|
@@ -11,15 +11,16 @@ This file is part of Network Pro.
|
|
|
11
11
|
* @description Runs Playwright E2E tests with desktop and root route assertions.
|
|
12
12
|
* @module tests/e2e
|
|
13
13
|
* @author Scott Lopez
|
|
14
|
-
* @updated 2025-
|
|
14
|
+
* @updated 2025-11-12
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { expect, test } from '@playwright/test';
|
|
18
18
|
import {
|
|
19
|
+
clickAndWaitForNavigation,
|
|
19
20
|
getFooter,
|
|
20
21
|
getVisibleNav,
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
gotoDesktop,
|
|
23
|
+
gotoMobile,
|
|
23
24
|
} from './shared/helpers.js';
|
|
24
25
|
|
|
25
26
|
// Root route should display nav bar and about link
|
|
@@ -29,9 +30,7 @@ test.describe('Desktop Tests', () => {
|
|
|
29
30
|
test("should display the navigation bar and 'about' link", async ({
|
|
30
31
|
page,
|
|
31
32
|
}) => {
|
|
32
|
-
await
|
|
33
|
-
await page.goto('/');
|
|
34
|
-
await page.waitForLoadState('domcontentloaded', { timeout: 60000 });
|
|
33
|
+
await gotoDesktop(page);
|
|
35
34
|
|
|
36
35
|
const nav = await getVisibleNav(page);
|
|
37
36
|
|
|
@@ -42,9 +41,7 @@ test.describe('Desktop Tests', () => {
|
|
|
42
41
|
|
|
43
42
|
// Root route should display the footer properly
|
|
44
43
|
test('should display the footer correctly', async ({ page }) => {
|
|
45
|
-
await
|
|
46
|
-
await page.goto('/');
|
|
47
|
-
await page.waitForLoadState('domcontentloaded', { timeout: 60000 });
|
|
44
|
+
await gotoDesktop(page);
|
|
48
45
|
|
|
49
46
|
const footer = getFooter(page);
|
|
50
47
|
await expect(footer).toBeVisible();
|
|
@@ -54,9 +51,7 @@ test.describe('Desktop Tests', () => {
|
|
|
54
51
|
test("should render the 'about' page with footer visible", async ({
|
|
55
52
|
page,
|
|
56
53
|
}) => {
|
|
57
|
-
await
|
|
58
|
-
await page.goto('/about');
|
|
59
|
-
await page.waitForLoadState('domcontentloaded', { timeout: 60000 });
|
|
54
|
+
await gotoDesktop(page, '/about');
|
|
60
55
|
|
|
61
56
|
const footer = getFooter(page);
|
|
62
57
|
await expect(footer).toBeVisible();
|
|
@@ -64,20 +59,16 @@ test.describe('Desktop Tests', () => {
|
|
|
64
59
|
|
|
65
60
|
// Root route should have a clickable "about" link
|
|
66
61
|
test("should ensure the 'about' link is clickable", async ({ page }) => {
|
|
67
|
-
await
|
|
68
|
-
await page.goto('/');
|
|
69
|
-
await page.waitForLoadState('domcontentloaded', { timeout: 60000 });
|
|
62
|
+
await gotoDesktop(page);
|
|
70
63
|
|
|
71
64
|
const nav = await getVisibleNav(page);
|
|
72
65
|
const aboutLink = nav.getByRole('link', { name: 'about' });
|
|
73
|
-
await expect(aboutLink).toBeVisible();
|
|
74
|
-
await aboutLink.click();
|
|
75
66
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
67
|
+
await clickAndWaitForNavigation(page, aboutLink, {
|
|
68
|
+
urlPattern: /\/about/,
|
|
69
|
+
timeout: 60000,
|
|
70
|
+
});
|
|
71
|
+
|
|
81
72
|
await expect(page).toHaveURL(/\/about/);
|
|
82
73
|
});
|
|
83
74
|
}); // End Desktop Tests
|
|
@@ -85,13 +76,10 @@ test.describe('Desktop Tests', () => {
|
|
|
85
76
|
// Root route should display headings properly on mobile
|
|
86
77
|
test.describe('Mobile Tests', () => {
|
|
87
78
|
test('should display main content correctly on mobile', async ({ page }) => {
|
|
88
|
-
await
|
|
89
|
-
await page.goto('/');
|
|
90
|
-
await page.waitForLoadState('domcontentloaded', { timeout: 60000 });
|
|
79
|
+
await gotoMobile(page);
|
|
91
80
|
|
|
92
|
-
|
|
93
|
-
|
|
81
|
+
// Ensure main headline is present on mobile
|
|
82
|
+
const h2 = page.locator('h2.index-title2');
|
|
83
|
+
await expect(h2).toContainText(/security/i);
|
|
94
84
|
});
|
|
95
85
|
});
|
|
96
|
-
|
|
97
|
-
// cspell:ignore domcontentloaded
|
package/tests/e2e/mobile.spec.js
CHANGED
|
@@ -11,7 +11,7 @@ This file is part of Network Pro.
|
|
|
11
11
|
* @description Runs Playwright E2E tests with mobile assertions.
|
|
12
12
|
* @module tests/e2e
|
|
13
13
|
* @author Scott Lopez
|
|
14
|
-
* @updated 2025-
|
|
14
|
+
* @updated 2025-11-12
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { expect, test } from '@playwright/test';
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
clickAndWaitForNavigation,
|
|
20
20
|
getFooter,
|
|
21
21
|
getVisibleNav,
|
|
22
|
-
|
|
22
|
+
gotoMobile,
|
|
23
23
|
} from './shared/helpers.js';
|
|
24
24
|
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
@@ -27,21 +27,20 @@ import {
|
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
28
28
|
|
|
29
29
|
test.describe('Mobile Tests', () => {
|
|
30
|
-
// Increase timeout for all mobile tests
|
|
31
30
|
test.setTimeout(90_000);
|
|
32
31
|
|
|
32
|
+
test.beforeEach(({ browserName }) => {
|
|
33
|
+
if (browserName === 'webkit')
|
|
34
|
+
test.skip('Skipping WebKit: manual validation only');
|
|
35
|
+
});
|
|
36
|
+
|
|
33
37
|
// -------------------------------------------------------------------------
|
|
34
38
|
// 🧱 Test 1: Main description text
|
|
35
39
|
// -------------------------------------------------------------------------
|
|
36
40
|
test('should display the main description text on mobile', async ({
|
|
37
41
|
page,
|
|
38
|
-
browserName,
|
|
39
42
|
}) => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
await setMobileView(page);
|
|
43
|
-
await page.goto('/');
|
|
44
|
-
await page.waitForLoadState('domcontentloaded', { timeout: 60_000 });
|
|
43
|
+
await gotoMobile(page);
|
|
45
44
|
|
|
46
45
|
const description = page.locator(
|
|
47
46
|
'div.index-title1:has-text("Locking Down Networks")',
|
|
@@ -52,18 +51,11 @@ test.describe('Mobile Tests', () => {
|
|
|
52
51
|
// -------------------------------------------------------------------------
|
|
53
52
|
// 🧱 Test 2: Main content
|
|
54
53
|
// -------------------------------------------------------------------------
|
|
55
|
-
test('should display main content correctly on mobile', async ({
|
|
56
|
-
page
|
|
57
|
-
browserName,
|
|
58
|
-
}) => {
|
|
59
|
-
if (browserName === 'webkit') test.skip();
|
|
60
|
-
|
|
61
|
-
await setMobileView(page);
|
|
62
|
-
await page.goto('/');
|
|
63
|
-
await page.waitForLoadState('domcontentloaded', { timeout: 60_000 });
|
|
54
|
+
test('should display main content correctly on mobile', async ({ page }) => {
|
|
55
|
+
await gotoMobile(page);
|
|
64
56
|
|
|
65
|
-
const mainHeading = page.locator('
|
|
66
|
-
await expect(mainHeading).
|
|
57
|
+
const mainHeading = page.locator('h2.index-title2');
|
|
58
|
+
await expect(mainHeading).toContainText(/security/i);
|
|
67
59
|
});
|
|
68
60
|
|
|
69
61
|
// -------------------------------------------------------------------------
|
|
@@ -71,41 +63,27 @@ test.describe('Mobile Tests', () => {
|
|
|
71
63
|
// -------------------------------------------------------------------------
|
|
72
64
|
test("should ensure the 'about' link is clickable on mobile", async ({
|
|
73
65
|
page,
|
|
74
|
-
browserName,
|
|
75
66
|
}) => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
await setMobileView(page);
|
|
79
|
-
await page.goto('/', { waitUntil: 'networkidle' });
|
|
67
|
+
await gotoMobile(page);
|
|
80
68
|
|
|
81
69
|
const nav = await getVisibleNav(page);
|
|
82
|
-
|
|
83
|
-
// Ensure the link exists before interacting
|
|
84
|
-
await page.waitForSelector('a[href="/about"]', { timeout: 10_000 });
|
|
85
70
|
const aboutLink = nav.getByRole('link', { name: 'about' });
|
|
86
71
|
|
|
87
|
-
// Use the safe navigation helper
|
|
88
72
|
await clickAndWaitForNavigation(page, aboutLink, {
|
|
89
73
|
urlPattern: /\/about/,
|
|
90
74
|
timeout: 60_000,
|
|
91
75
|
});
|
|
76
|
+
|
|
77
|
+
await expect(page).toHaveURL(/\/about/);
|
|
92
78
|
});
|
|
93
79
|
|
|
94
80
|
// -------------------------------------------------------------------------
|
|
95
81
|
// 🧱 Test 4: Footer presence on /about
|
|
96
82
|
// -------------------------------------------------------------------------
|
|
97
|
-
test('should display the footer on /about (mobile)', async ({
|
|
98
|
-
page,
|
|
99
|
-
browserName,
|
|
100
|
-
}) => {
|
|
101
|
-
if (browserName === 'webkit') test.skip();
|
|
102
|
-
|
|
103
|
-
await setMobileView(page);
|
|
104
|
-
await page.goto('/about');
|
|
83
|
+
test('should display the footer on /about (mobile)', async ({ page }) => {
|
|
84
|
+
await gotoMobile(page, '/about');
|
|
105
85
|
|
|
106
86
|
const footer = getFooter(page);
|
|
107
87
|
await expect(footer).toBeVisible();
|
|
108
88
|
});
|
|
109
89
|
});
|
|
110
|
-
|
|
111
|
-
// cspell:ignore domcontentloaded networkidle
|
|
@@ -11,19 +11,19 @@ This file is part of Network Pro.
|
|
|
11
11
|
* @description Stores commonly used functions for importing into E2E tests.
|
|
12
12
|
* @module tests/e2e/shared
|
|
13
13
|
* @author Scott Lopez
|
|
14
|
-
* @updated 2025-
|
|
14
|
+
* @updated 2025-11-12
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
const DEBUG_LOGS = false; // set to true to enable console logs
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* @param {import('@playwright/test').Page} page
|
|
20
|
+
* @param {import('@playwright/test').Page} page
|
|
21
21
|
* @returns {Promise<void>}
|
|
22
|
-
* @description Sets
|
|
22
|
+
* @description Sets desktop viewport and allows layout to settle.
|
|
23
23
|
*/
|
|
24
24
|
export async function setDesktopView(page) {
|
|
25
25
|
await page.setViewportSize({ width: 1280, height: 720 });
|
|
26
|
-
await page.waitForTimeout(1500);
|
|
26
|
+
await page.waitForTimeout(1500); // Known-stable stabilization delay
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -36,18 +36,47 @@ export async function setMobileView(page) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
*
|
|
40
|
-
* @
|
|
41
|
-
* @
|
|
39
|
+
* Navigate with desktop viewport + initial DOM load.
|
|
40
|
+
* @param {import('@playwright/test').Page} page
|
|
41
|
+
* @param {string} path
|
|
42
|
+
*/
|
|
43
|
+
export async function gotoDesktop(page, path = '/') {
|
|
44
|
+
await setDesktopView(page);
|
|
45
|
+
await page.goto(path, { waitUntil: 'domcontentloaded', timeout: 60000 });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Navigate with mobile viewport + initial DOM load.
|
|
50
|
+
* @param {import('@playwright/test').Page} page
|
|
51
|
+
* @param {string} path
|
|
52
|
+
*/
|
|
53
|
+
export async function gotoMobile(page, path = '/') {
|
|
54
|
+
await setMobileView(page);
|
|
55
|
+
await page.goto(path, { waitUntil: 'domcontentloaded', timeout: 60000 });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Returns the visible navigation region.
|
|
60
|
+
* @param {import('@playwright/test').Page} page
|
|
61
|
+
* @returns {Promise<import('@playwright/test').Locator>}
|
|
42
62
|
*/
|
|
43
63
|
export async function getVisibleNav(page) {
|
|
44
64
|
const navHome = page.getByRole('navigation', { name: 'Homepage navigation' });
|
|
45
65
|
const navMain = page.getByRole('navigation', { name: 'Main navigation' });
|
|
46
66
|
|
|
47
|
-
|
|
48
|
-
|
|
67
|
+
const timeout = 5000;
|
|
68
|
+
|
|
69
|
+
if (await navHome.isVisible({ timeout }).catch(() => false)) {
|
|
70
|
+
if (DEBUG_LOGS) console.log('✅ Detected visible nav: Homepage navigation');
|
|
71
|
+
return navHome;
|
|
72
|
+
}
|
|
49
73
|
|
|
50
|
-
|
|
74
|
+
if (await navMain.isVisible({ timeout }).catch(() => false)) {
|
|
75
|
+
if (DEBUG_LOGS) console.log('✅ Detected visible nav: Main navigation');
|
|
76
|
+
return navMain;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
throw new Error('❌ No visible navigation element found within timeout.');
|
|
51
80
|
}
|
|
52
81
|
|
|
53
82
|
/**
|
|
@@ -59,37 +88,28 @@ export function getFooter(page) {
|
|
|
59
88
|
}
|
|
60
89
|
|
|
61
90
|
/**
|
|
62
|
-
*
|
|
63
|
-
* @description Clicks a link or button and waits for navigation or URL change.
|
|
64
|
-
* Works for both SPA (client-side) and full-page navigations.
|
|
91
|
+
* Click + wait for SPA or full navigation event.
|
|
65
92
|
*
|
|
66
93
|
* @param {import('@playwright/test').Page} page
|
|
67
94
|
* @param {import('@playwright/test').Locator} locator
|
|
68
|
-
* @param {
|
|
69
|
-
* @param {string|RegExp} [options.urlPattern] - URL or regex to match
|
|
70
|
-
* @param {number} [options.timeout=60000] - Max wait time
|
|
95
|
+
* @param {{ urlPattern?: string | RegExp, timeout?: number }} [options]
|
|
71
96
|
*/
|
|
72
97
|
export async function clickAndWaitForNavigation(page, locator, options = {}) {
|
|
73
98
|
const { urlPattern = /\/.*/, timeout = 60000 } = options;
|
|
74
99
|
|
|
75
|
-
// Ensure the element is ready for interaction
|
|
76
100
|
await locator.scrollIntoViewIfNeeded();
|
|
77
|
-
await locator.waitFor({ state: 'visible', timeout:
|
|
78
|
-
await locator.click({ trial: true });
|
|
101
|
+
await locator.waitFor({ state: 'visible', timeout: 60000 });
|
|
79
102
|
|
|
80
|
-
|
|
81
|
-
const currentUrl = page.url();
|
|
103
|
+
const previousURL = page.url();
|
|
82
104
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
105
|
+
const [, newURL] = await Promise.all([
|
|
106
|
+
page.waitForURL(
|
|
107
|
+
(url) =>
|
|
108
|
+
url.toString() !== previousURL && urlPattern.test(url.toString()),
|
|
109
|
+
{ timeout },
|
|
110
|
+
),
|
|
111
|
+
locator.click().then(() => page.url()),
|
|
88
112
|
]);
|
|
89
113
|
|
|
90
|
-
|
|
91
|
-
await expect(page).toHaveURL(urlPattern, { timeout });
|
|
92
|
-
|
|
93
|
-
// Optional: log navigation success (helps in CI)
|
|
94
|
-
console.log(`✅ Navigation from ${currentUrl} → ${page.url()}`);
|
|
114
|
+
console.log(`✅ Navigation from ${previousURL} → ${newURL}`);
|
|
95
115
|
}
|