@fdm-monster/client-next 2.2.2 → 2.2.3
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/.yarn/install-state.gz +0 -0
- package/README.md +19 -0
- package/RELEASE_NOTES.MD +10 -0
- package/dist/assets/{index-BlOaSQti.js → index-BAB7cJ3l.js} +52 -52
- package/dist/assets/{index-BlOaSQti.js.map → index-BAB7cJ3l.js.map} +1 -1
- package/dist/assets/index-DfA7W6iO.css +1 -0
- package/dist/index.html +3 -3
- package/package.json +21 -2
- package/screenshots/COVERAGE.md +383 -0
- package/screenshots/README.md +431 -0
- package/screenshots/fixtures/api-mock.ts +699 -0
- package/screenshots/fixtures/data/auth.fixtures.ts +79 -0
- package/screenshots/fixtures/data/cameras.fixtures.ts +48 -0
- package/screenshots/fixtures/data/files.fixtures.ts +56 -0
- package/screenshots/fixtures/data/floors.fixtures.ts +39 -0
- package/screenshots/fixtures/data/jobs.fixtures.ts +172 -0
- package/screenshots/fixtures/data/printers.fixtures.ts +132 -0
- package/screenshots/fixtures/data/settings.fixtures.ts +62 -0
- package/screenshots/fixtures/socketio-mock.ts +76 -0
- package/screenshots/fixtures/test-fixtures.ts +112 -0
- package/screenshots/helpers/dialog.helper.ts +196 -0
- package/screenshots/helpers/form.helper.ts +207 -0
- package/screenshots/helpers/navigation.helper.ts +191 -0
- package/screenshots/playwright.screenshots.config.ts +70 -0
- package/screenshots/suites/00-example.screenshots.spec.ts +29 -0
- package/screenshots/suites/01-auth.screenshots.spec.ts +130 -0
- package/screenshots/suites/02-dashboard.screenshots.spec.ts +106 -0
- package/screenshots/suites/03-printer-grid.screenshots.spec.ts +160 -0
- package/screenshots/suites/04-printer-list.screenshots.spec.ts +184 -0
- package/screenshots/suites/05-camera-grid.screenshots.spec.ts +127 -0
- package/screenshots/suites/06-print-jobs.screenshots.spec.ts +139 -0
- package/screenshots/suites/07-queue.screenshots.spec.ts +86 -0
- package/screenshots/suites/08-files.screenshots.spec.ts +142 -0
- package/screenshots/suites/09-settings.screenshots.spec.ts +130 -0
- package/screenshots/suites/10-panels-dialogs.screenshots.spec.ts +245 -0
- package/screenshots/utils.ts +216 -0
- package/vitest.config.ts +8 -0
- package/dist/assets/index-TeWdSn_6.css +0 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Playwright configuration for automated documentation screenshots
|
|
5
|
+
* Chrome-only, desktop-focused configuration for screenshot automation
|
|
6
|
+
* See https://playwright.dev/docs/test-configuration
|
|
7
|
+
*/
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
testDir: './suites',
|
|
10
|
+
|
|
11
|
+
// Screenshot tests can take longer due to waiting for animations
|
|
12
|
+
timeout: 90 * 1000,
|
|
13
|
+
|
|
14
|
+
// Run tests sequentially for predictable screenshot generation
|
|
15
|
+
fullyParallel: false,
|
|
16
|
+
|
|
17
|
+
// Fail the build on CI if you accidentally left test.only in the source code
|
|
18
|
+
forbidOnly: !!process.env.CI,
|
|
19
|
+
|
|
20
|
+
// Retry on CI only
|
|
21
|
+
retries: process.env.CI ? 2 : 0,
|
|
22
|
+
|
|
23
|
+
// Reporters
|
|
24
|
+
reporter: [
|
|
25
|
+
['html', { outputFolder: 'playwright-report' }],
|
|
26
|
+
['list']
|
|
27
|
+
],
|
|
28
|
+
|
|
29
|
+
// Shared settings for all projects
|
|
30
|
+
use: {
|
|
31
|
+
// Base URL to use in actions like `await page.goto('/')`
|
|
32
|
+
baseURL: process.env.BASE_URL || 'http://localhost:3000',
|
|
33
|
+
|
|
34
|
+
// Collect trace when retrying failed tests
|
|
35
|
+
trace: 'retain-on-failure',
|
|
36
|
+
|
|
37
|
+
// Screenshot configuration
|
|
38
|
+
screenshot: 'only-on-failure',
|
|
39
|
+
|
|
40
|
+
// Video configuration - only on failure to help debug
|
|
41
|
+
video: 'retain-on-failure',
|
|
42
|
+
|
|
43
|
+
// Consistent viewport for all desktop screenshots
|
|
44
|
+
viewport: { width: 1920, height: 1080 },
|
|
45
|
+
|
|
46
|
+
// Allow more time for screenshot operations
|
|
47
|
+
actionTimeout: 10 * 1000,
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
// CHROME ONLY - Desktop focus, no mobile/tablet matrix (for now)
|
|
51
|
+
projects: [
|
|
52
|
+
{
|
|
53
|
+
name: 'chrome-screenshots',
|
|
54
|
+
use: {
|
|
55
|
+
...devices['Desktop Chrome'],
|
|
56
|
+
viewport: { width: 1920, height: 1080 },
|
|
57
|
+
// Enable headed mode via HEADED=1 environment variable for debugging
|
|
58
|
+
headless: !process.env.HEADED,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
|
|
63
|
+
// Conditional webServer - skip if SKIP_DEV_SERVER=1 (for CI where server runs separately)
|
|
64
|
+
webServer: process.env.SKIP_DEV_SERVER ? undefined : {
|
|
65
|
+
command: 'yarn dev',
|
|
66
|
+
url: 'http://localhost:3000',
|
|
67
|
+
reuseExistingServer: !process.env.CI,
|
|
68
|
+
timeout: 120 * 1000,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { test, expect } from '../fixtures/test-fixtures';
|
|
2
|
+
import { captureFullPage } from '../utils';
|
|
3
|
+
import { createNavigationHelper } from '../helpers/navigation.helper';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Example Screenshot Test
|
|
7
|
+
* A single, complete example showing the full workflow
|
|
8
|
+
*/
|
|
9
|
+
test('example-dashboard-screenshot', async ({ authenticatedPage, apiMock }) => {
|
|
10
|
+
// 1. Mock all API endpoints with default data
|
|
11
|
+
await apiMock.mockAllEndpoints({ loginRequired: false });
|
|
12
|
+
|
|
13
|
+
// 2. Navigate to the page you want to capture
|
|
14
|
+
const nav = createNavigationHelper(authenticatedPage);
|
|
15
|
+
await nav.goToDashboard();
|
|
16
|
+
|
|
17
|
+
// 3. Wait for content to load
|
|
18
|
+
await authenticatedPage.waitForSelector('main, [data-testid="dashboard"]', {
|
|
19
|
+
timeout: 5000,
|
|
20
|
+
}).catch(() => {});
|
|
21
|
+
|
|
22
|
+
// 4. Optional: wait a bit more for animations/dynamic content
|
|
23
|
+
await authenticatedPage.waitForTimeout(1000);
|
|
24
|
+
|
|
25
|
+
// 5. Capture the screenshot
|
|
26
|
+
await captureFullPage(authenticatedPage, 'example-dashboard.png', 'examples');
|
|
27
|
+
|
|
28
|
+
// Done! Screenshot saved to screenshots/output/examples/example-dashboard.png
|
|
29
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { test, expect } from '../fixtures/test-fixtures';
|
|
2
|
+
import { captureFullPage, captureElement } from '../utils';
|
|
3
|
+
import { createFormHelper } from '../helpers/form.helper';
|
|
4
|
+
import { createNavigationHelper } from '../helpers/navigation.helper';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Authentication Screenshots
|
|
8
|
+
* Captures: First Time Setup, Login, Registration pages
|
|
9
|
+
*/
|
|
10
|
+
test.describe('Authentication Screenshots', () => {
|
|
11
|
+
test('01-first-time-setup-wizard', async ({ page, apiMock }) => {
|
|
12
|
+
// Mock all endpoints with wizard incomplete
|
|
13
|
+
await apiMock.mockAllEndpoints({
|
|
14
|
+
loginRequired: false,
|
|
15
|
+
emptyData: false,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Override auth endpoints to show wizard incomplete
|
|
19
|
+
await apiMock.mockAuthEndpoints({
|
|
20
|
+
loginRequired: false,
|
|
21
|
+
wizardIncomplete: true,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Navigate to first time setup
|
|
25
|
+
const nav = createNavigationHelper(page);
|
|
26
|
+
await nav.goToFirstTimeSetup();
|
|
27
|
+
|
|
28
|
+
// Wait for page to render
|
|
29
|
+
await page.waitForSelector('[data-testid="wizard-container"], .first-time-setup, .wizard', {
|
|
30
|
+
timeout: 5000,
|
|
31
|
+
}).catch(() => {});
|
|
32
|
+
|
|
33
|
+
// Capture the wizard page
|
|
34
|
+
await captureFullPage(page, 'first-time-setup-wizard.png', 'auth');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('02-login-page', async ({ page, apiMock }) => {
|
|
38
|
+
// Mock all endpoints with login required
|
|
39
|
+
await apiMock.mockAllEndpoints({ loginRequired: true });
|
|
40
|
+
|
|
41
|
+
// Navigate to login page
|
|
42
|
+
const nav = createNavigationHelper(page);
|
|
43
|
+
await nav.goToLogin();
|
|
44
|
+
|
|
45
|
+
// Wait for login form
|
|
46
|
+
await page.waitForSelector('[name="username"], input[type="text"]', {
|
|
47
|
+
timeout: 5000,
|
|
48
|
+
}).catch(() => {});
|
|
49
|
+
|
|
50
|
+
// Capture empty login page
|
|
51
|
+
await captureFullPage(page, 'login-page-empty.png', 'auth');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('03-login-page-filled', async ({ page, apiMock }) => {
|
|
55
|
+
// Mock all endpoints with login required
|
|
56
|
+
await apiMock.mockAllEndpoints({ loginRequired: true });
|
|
57
|
+
|
|
58
|
+
// Navigate to login page
|
|
59
|
+
const nav = createNavigationHelper(page);
|
|
60
|
+
await nav.goToLogin();
|
|
61
|
+
|
|
62
|
+
// Wait for login form - try multiple selectors
|
|
63
|
+
await page.waitForSelector('input[type="text"], input[type="email"], .v-text-field input', {
|
|
64
|
+
timeout: 5000,
|
|
65
|
+
}).catch(() => {});
|
|
66
|
+
|
|
67
|
+
await page.waitForTimeout(1000);
|
|
68
|
+
|
|
69
|
+
// Fill login form directly without helper (more reliable)
|
|
70
|
+
const usernameInput = page.locator('input').first();
|
|
71
|
+
const passwordInput = page.locator('input[type="password"]').first();
|
|
72
|
+
|
|
73
|
+
await usernameInput.fill('admin');
|
|
74
|
+
await passwordInput.fill('password123');
|
|
75
|
+
|
|
76
|
+
// Capture filled login page
|
|
77
|
+
await captureFullPage(page, 'login-page-filled.png', 'auth');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('04-registration-page', async ({ page, apiMock }) => {
|
|
81
|
+
// Mock all endpoints first
|
|
82
|
+
await apiMock.mockAllEndpoints({ loginRequired: false });
|
|
83
|
+
|
|
84
|
+
// Override auth with registration enabled
|
|
85
|
+
await apiMock.mockAuthEndpoints({ registrationEnabled: true });
|
|
86
|
+
|
|
87
|
+
// Navigate to registration page
|
|
88
|
+
const nav = createNavigationHelper(page);
|
|
89
|
+
await nav.goToRegister();
|
|
90
|
+
|
|
91
|
+
// Wait for registration form
|
|
92
|
+
await page.waitForSelector('[name="username"], input[type="text"]', {
|
|
93
|
+
timeout: 5000,
|
|
94
|
+
}).catch(() => {});
|
|
95
|
+
|
|
96
|
+
// Capture empty registration page
|
|
97
|
+
await captureFullPage(page, 'registration-page-empty.png', 'auth');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('05-registration-page-filled', async ({ page, apiMock }) => {
|
|
101
|
+
// Mock all endpoints first
|
|
102
|
+
await apiMock.mockAllEndpoints({ loginRequired: false });
|
|
103
|
+
|
|
104
|
+
// Override auth with registration enabled
|
|
105
|
+
await apiMock.mockAuthEndpoints({ registrationEnabled: true });
|
|
106
|
+
|
|
107
|
+
// Navigate to registration page
|
|
108
|
+
const nav = createNavigationHelper(page);
|
|
109
|
+
await nav.goToRegister();
|
|
110
|
+
|
|
111
|
+
// Wait for registration form
|
|
112
|
+
await page.waitForSelector('input[type="text"], input[type="email"], .v-text-field input', {
|
|
113
|
+
timeout: 5000,
|
|
114
|
+
}).catch(() => {});
|
|
115
|
+
|
|
116
|
+
await page.waitForTimeout(1000);
|
|
117
|
+
|
|
118
|
+
// Fill registration form directly (more reliable)
|
|
119
|
+
const inputs = page.locator('input');
|
|
120
|
+
const usernameInput = inputs.first();
|
|
121
|
+
const passwordInputs = page.locator('input[type="password"]');
|
|
122
|
+
|
|
123
|
+
await usernameInput.fill('newuser');
|
|
124
|
+
await passwordInputs.first().fill('securePassword123');
|
|
125
|
+
await passwordInputs.nth(1).fill('securePassword123');
|
|
126
|
+
|
|
127
|
+
// Capture filled registration page
|
|
128
|
+
await captureFullPage(page, 'registration-page-filled.png', 'auth');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { test, expect } from '../fixtures/test-fixtures';
|
|
2
|
+
import { captureFullPage, captureViewport } from '../utils';
|
|
3
|
+
import { createNavigationHelper } from '../helpers/navigation.helper';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Dashboard Screenshots
|
|
7
|
+
* Captures: Dashboard overview, stats widgets, recent activity
|
|
8
|
+
*/
|
|
9
|
+
test.describe('Dashboard Screenshots', () => {
|
|
10
|
+
test.beforeEach(async ({ apiMock }) => {
|
|
11
|
+
// Mock all endpoints for authenticated dashboard
|
|
12
|
+
await apiMock.mockAllEndpoints({ loginRequired: false });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('01-dashboard-overview', async ({ authenticatedPage }) => {
|
|
16
|
+
const nav = createNavigationHelper(authenticatedPage);
|
|
17
|
+
|
|
18
|
+
// Navigate to dashboard
|
|
19
|
+
await nav.goToDashboard();
|
|
20
|
+
|
|
21
|
+
// Wait for dashboard content to load
|
|
22
|
+
await authenticatedPage.waitForSelector(
|
|
23
|
+
'[data-testid="dashboard"], .dashboard, main',
|
|
24
|
+
{ timeout: 5000 }
|
|
25
|
+
).catch(() => {});
|
|
26
|
+
|
|
27
|
+
// Additional wait for stats to populate
|
|
28
|
+
await authenticatedPage.waitForTimeout(1000);
|
|
29
|
+
|
|
30
|
+
// Capture full dashboard page
|
|
31
|
+
await captureFullPage(authenticatedPage, 'dashboard-overview.png', 'dashboard');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('02-dashboard-viewport', async ({ authenticatedPage }) => {
|
|
35
|
+
const nav = createNavigationHelper(authenticatedPage);
|
|
36
|
+
|
|
37
|
+
// Navigate to dashboard
|
|
38
|
+
await nav.goToDashboard();
|
|
39
|
+
|
|
40
|
+
// Wait for dashboard content
|
|
41
|
+
await authenticatedPage.waitForSelector(
|
|
42
|
+
'[data-testid="dashboard"], .dashboard, main',
|
|
43
|
+
{ timeout: 5000 }
|
|
44
|
+
).catch(() => {});
|
|
45
|
+
|
|
46
|
+
await authenticatedPage.waitForTimeout(1000);
|
|
47
|
+
|
|
48
|
+
// Capture viewport (above the fold)
|
|
49
|
+
await captureViewport(authenticatedPage, 'dashboard-viewport.png', 'dashboard');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('03-dashboard-stats-widgets', async ({ authenticatedPage }) => {
|
|
53
|
+
const nav = createNavigationHelper(authenticatedPage);
|
|
54
|
+
|
|
55
|
+
// Navigate to dashboard
|
|
56
|
+
await nav.goToDashboard();
|
|
57
|
+
|
|
58
|
+
// Wait for stats widgets
|
|
59
|
+
await authenticatedPage.waitForSelector(
|
|
60
|
+
'[data-testid="stats-widget"], .stats-card, .statistics',
|
|
61
|
+
{ timeout: 5000 }
|
|
62
|
+
).catch(() => {});
|
|
63
|
+
|
|
64
|
+
await authenticatedPage.waitForTimeout(1000);
|
|
65
|
+
|
|
66
|
+
// Try to capture stats section if it exists
|
|
67
|
+
const statsSection = authenticatedPage.locator(
|
|
68
|
+
'[data-testid="stats-section"], .stats-container, .dashboard-stats'
|
|
69
|
+
).first();
|
|
70
|
+
|
|
71
|
+
if (await statsSection.isVisible().catch(() => false)) {
|
|
72
|
+
await statsSection.screenshot({
|
|
73
|
+
path: 'screenshots/output/dashboard/stats-widgets.png',
|
|
74
|
+
});
|
|
75
|
+
} else {
|
|
76
|
+
// Fallback to full page if specific stats section not found
|
|
77
|
+
await captureFullPage(authenticatedPage, 'stats-widgets.png', 'dashboard');
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('04-dashboard-empty-state', async ({ page, apiMock }) => {
|
|
82
|
+
// Mock with empty data
|
|
83
|
+
await apiMock.mockAllEndpoints({ loginRequired: false, emptyData: true });
|
|
84
|
+
|
|
85
|
+
// Setup authenticated page
|
|
86
|
+
await page.goto('/');
|
|
87
|
+
await page.evaluate(() => {
|
|
88
|
+
localStorage.setItem('auth-token', 'mock-jwt-token');
|
|
89
|
+
localStorage.setItem('refresh-token', 'mock-refresh-token');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const nav = createNavigationHelper(page);
|
|
93
|
+
await nav.goToDashboard();
|
|
94
|
+
|
|
95
|
+
// Wait for empty state
|
|
96
|
+
await page.waitForSelector(
|
|
97
|
+
'[data-testid="empty-state"], .empty-state, main',
|
|
98
|
+
{ timeout: 5000 }
|
|
99
|
+
).catch(() => {});
|
|
100
|
+
|
|
101
|
+
await page.waitForTimeout(1000);
|
|
102
|
+
|
|
103
|
+
// Capture empty dashboard
|
|
104
|
+
await captureFullPage(page, 'dashboard-empty-state.png', 'dashboard');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { test, expect } from '../fixtures/test-fixtures';
|
|
2
|
+
import { captureFullPage, captureElement } from '../utils';
|
|
3
|
+
import { createNavigationHelper } from '../helpers/navigation.helper';
|
|
4
|
+
import { createDialogHelper } from '../helpers/dialog.helper';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Printer Grid Screenshots
|
|
8
|
+
* Captures: Empty grid, grid with printers, creating floor/printer, drag operations, batch bar, upload progress
|
|
9
|
+
*/
|
|
10
|
+
test.describe('Printer Grid Screenshots', () => {
|
|
11
|
+
test.beforeEach(async ({ apiMock }) => {
|
|
12
|
+
await apiMock.mockAllEndpoints({ loginRequired: false });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('01-printer-grid-empty', async ({ authenticatedPage, apiMock }) => {
|
|
16
|
+
// Mock with empty data
|
|
17
|
+
await apiMock.mockAllEndpoints({ loginRequired: false, emptyData: true });
|
|
18
|
+
|
|
19
|
+
const nav = createNavigationHelper(authenticatedPage);
|
|
20
|
+
await nav.goToPrinterGrid();
|
|
21
|
+
|
|
22
|
+
// Wait for grid to load
|
|
23
|
+
await authenticatedPage.waitForSelector(
|
|
24
|
+
'[data-testid="printer-grid"], .printer-grid, main',
|
|
25
|
+
{ timeout: 5000 }
|
|
26
|
+
).catch(() => {});
|
|
27
|
+
|
|
28
|
+
await authenticatedPage.waitForTimeout(1000);
|
|
29
|
+
|
|
30
|
+
await captureFullPage(authenticatedPage, 'printer-grid-empty.png', 'printer-grid');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('02-printer-grid-with-printers', async ({ authenticatedPage, apiMock }) => {
|
|
34
|
+
// Explicitly mock with data (not empty)
|
|
35
|
+
await apiMock.mockAllEndpoints({ loginRequired: false, emptyData: false });
|
|
36
|
+
|
|
37
|
+
const nav = createNavigationHelper(authenticatedPage);
|
|
38
|
+
await nav.goToPrinterGrid();
|
|
39
|
+
|
|
40
|
+
// Wait for toolbar with floor tabs to appear (indicates floors are loaded)
|
|
41
|
+
await authenticatedPage.waitForSelector(
|
|
42
|
+
'v-btn-toggle:has(button:has-text("Main Workshop")), .v-btn-group:has(button:has-text("Main")), button:has-text("Main")',
|
|
43
|
+
{ timeout: 5000 }
|
|
44
|
+
).catch(() => {});
|
|
45
|
+
|
|
46
|
+
// Also wait for printer tiles to appear
|
|
47
|
+
await authenticatedPage.waitForSelector(
|
|
48
|
+
'.printer-card, [data-testid="printer-card"], .printer-tile',
|
|
49
|
+
{ timeout: 5000 }
|
|
50
|
+
).catch(() => {});
|
|
51
|
+
|
|
52
|
+
await authenticatedPage.waitForTimeout(1500);
|
|
53
|
+
|
|
54
|
+
await captureFullPage(authenticatedPage, 'printer-grid-with-printers.png', 'printer-grid');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('03-create-floor-dialog', async ({ authenticatedPage, apiMock }) => {
|
|
58
|
+
// Use empty floors so "Create First Floor" button appears
|
|
59
|
+
await apiMock.mockAllEndpoints({ loginRequired: false, emptyData: true });
|
|
60
|
+
|
|
61
|
+
const nav = createNavigationHelper(authenticatedPage);
|
|
62
|
+
await nav.goToPrinterGrid();
|
|
63
|
+
|
|
64
|
+
await authenticatedPage.waitForTimeout(1000);
|
|
65
|
+
|
|
66
|
+
// Click "Create First Floor" button (only appears when no floors exist)
|
|
67
|
+
const createFloorBtn = authenticatedPage.locator('button').filter({ hasText: /create.*first.*floor/i }).first();
|
|
68
|
+
await createFloorBtn.click();
|
|
69
|
+
|
|
70
|
+
// Wait for dialog
|
|
71
|
+
const dialog = createDialogHelper(authenticatedPage);
|
|
72
|
+
await dialog.waitForDialog();
|
|
73
|
+
|
|
74
|
+
await authenticatedPage.waitForTimeout(500);
|
|
75
|
+
|
|
76
|
+
await captureFullPage(authenticatedPage, 'create-floor-dialog.png', 'printer-grid');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('04-create-printer-dialog', async ({ authenticatedPage, apiMock }) => {
|
|
80
|
+
// Mock with data so printers list page shows normally
|
|
81
|
+
await apiMock.mockAllEndpoints({ loginRequired: false, emptyData: false });
|
|
82
|
+
|
|
83
|
+
// Navigate to Printer List page (not grid) where "Create Printer" button exists
|
|
84
|
+
const nav = createNavigationHelper(authenticatedPage);
|
|
85
|
+
await nav.goToPrinterList();
|
|
86
|
+
|
|
87
|
+
await authenticatedPage.waitForTimeout(1000);
|
|
88
|
+
|
|
89
|
+
// Click "Create Printer" button
|
|
90
|
+
const createPrinterBtn = authenticatedPage.locator('button').filter({ hasText: /create.*printer/i }).first();
|
|
91
|
+
await createPrinterBtn.click();
|
|
92
|
+
|
|
93
|
+
// Wait for dialog
|
|
94
|
+
const dialog = createDialogHelper(authenticatedPage);
|
|
95
|
+
await dialog.waitForDialog();
|
|
96
|
+
|
|
97
|
+
await authenticatedPage.waitForTimeout(500);
|
|
98
|
+
|
|
99
|
+
await captureFullPage(authenticatedPage, 'create-printer-dialog.png', 'printer-grid');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('05-printer-grid-drag-hint', async ({ authenticatedPage, apiMock }) => {
|
|
103
|
+
await apiMock.mockAllEndpoints({ loginRequired: false, emptyData: false });
|
|
104
|
+
|
|
105
|
+
const nav = createNavigationHelper(authenticatedPage);
|
|
106
|
+
await nav.goToPrinterGrid();
|
|
107
|
+
|
|
108
|
+
await authenticatedPage.waitForTimeout(1000);
|
|
109
|
+
|
|
110
|
+
// Try to trigger drag state by hovering over a printer card
|
|
111
|
+
const printerCard = authenticatedPage.locator('.printer-card, [data-testid="printer-card"]').first();
|
|
112
|
+
if (await printerCard.isVisible().catch(() => false)) {
|
|
113
|
+
await printerCard.hover();
|
|
114
|
+
await authenticatedPage.waitForTimeout(500);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await captureFullPage(authenticatedPage, 'printer-grid-drag-hint.png', 'printer-grid');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('06-batch-operations-bar', async ({ authenticatedPage, apiMock }) => {
|
|
121
|
+
await apiMock.mockAllEndpoints({ loginRequired: false, emptyData: false });
|
|
122
|
+
|
|
123
|
+
const nav = createNavigationHelper(authenticatedPage);
|
|
124
|
+
await nav.goToPrinterGrid();
|
|
125
|
+
|
|
126
|
+
await authenticatedPage.waitForTimeout(1000);
|
|
127
|
+
|
|
128
|
+
// Try to select multiple printers
|
|
129
|
+
const printerCards = authenticatedPage.locator('.printer-card, [data-testid="printer-card"]');
|
|
130
|
+
const count = await printerCards.count().catch(() => 0);
|
|
131
|
+
|
|
132
|
+
if (count >= 2) {
|
|
133
|
+
// Click first printer with shift/ctrl
|
|
134
|
+
await printerCards.nth(0).click({ modifiers: ['Shift'] });
|
|
135
|
+
await authenticatedPage.waitForTimeout(300);
|
|
136
|
+
await printerCards.nth(1).click({ modifiers: ['Shift'] });
|
|
137
|
+
await authenticatedPage.waitForTimeout(500);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
await captureFullPage(authenticatedPage, 'batch-operations-bar.png', 'printer-grid');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('07-printer-grid-context-menu', async ({ authenticatedPage, apiMock }) => {
|
|
144
|
+
await apiMock.mockAllEndpoints({ loginRequired: false, emptyData: false });
|
|
145
|
+
|
|
146
|
+
const nav = createNavigationHelper(authenticatedPage);
|
|
147
|
+
await nav.goToPrinterGrid();
|
|
148
|
+
|
|
149
|
+
await authenticatedPage.waitForTimeout(1000);
|
|
150
|
+
|
|
151
|
+
// Right-click on a printer card
|
|
152
|
+
const printerCard = authenticatedPage.locator('.printer-card, [data-testid="printer-card"]').first();
|
|
153
|
+
if (await printerCard.isVisible().catch(() => false)) {
|
|
154
|
+
await printerCard.click({ button: 'right' });
|
|
155
|
+
await authenticatedPage.waitForTimeout(500);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
await captureFullPage(authenticatedPage, 'printer-grid-context-menu.png', 'printer-grid');
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { test, expect } from '../fixtures/test-fixtures';
|
|
2
|
+
import { captureFullPage } from '../utils';
|
|
3
|
+
import { createNavigationHelper } from '../helpers/navigation.helper';
|
|
4
|
+
import { createDialogHelper } from '../helpers/dialog.helper';
|
|
5
|
+
import { createFormHelper } from '../helpers/form.helper';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Printer List Screenshots
|
|
9
|
+
* Captures: List view, creating printer, attaching floor/camera/tag
|
|
10
|
+
*/
|
|
11
|
+
test.describe('Printer List Screenshots', () => {
|
|
12
|
+
test.beforeEach(async ({ apiMock }) => {
|
|
13
|
+
await apiMock.mockAllEndpoints({ loginRequired: false });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('01-printer-list-view', async ({ authenticatedPage }) => {
|
|
17
|
+
const nav = createNavigationHelper(authenticatedPage);
|
|
18
|
+
await nav.goToPrinterList();
|
|
19
|
+
|
|
20
|
+
// Wait for list to load
|
|
21
|
+
await authenticatedPage.waitForSelector(
|
|
22
|
+
'[data-testid="printer-list"], .printer-list, table, .v-data-table',
|
|
23
|
+
{ timeout: 5000 }
|
|
24
|
+
).catch(() => {});
|
|
25
|
+
|
|
26
|
+
await authenticatedPage.waitForTimeout(1000);
|
|
27
|
+
|
|
28
|
+
await captureFullPage(authenticatedPage, 'printer-list-view.png', 'printer-list');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('02-printer-list-empty', async ({ authenticatedPage, apiMock }) => {
|
|
32
|
+
// Mock with empty data
|
|
33
|
+
await apiMock.mockAllEndpoints({ loginRequired: false, emptyData: true });
|
|
34
|
+
|
|
35
|
+
const nav = createNavigationHelper(authenticatedPage);
|
|
36
|
+
await nav.goToPrinterList();
|
|
37
|
+
|
|
38
|
+
await authenticatedPage.waitForSelector(
|
|
39
|
+
'[data-testid="empty-state"], .empty-state, main',
|
|
40
|
+
{ timeout: 5000 }
|
|
41
|
+
).catch(() => {});
|
|
42
|
+
|
|
43
|
+
await authenticatedPage.waitForTimeout(1000);
|
|
44
|
+
|
|
45
|
+
await captureFullPage(authenticatedPage, 'printer-list-empty.png', 'printer-list');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('03-create-printer-dialog', async ({ authenticatedPage }) => {
|
|
49
|
+
const nav = createNavigationHelper(authenticatedPage);
|
|
50
|
+
await nav.goToPrinterList();
|
|
51
|
+
|
|
52
|
+
await authenticatedPage.waitForTimeout(1000);
|
|
53
|
+
|
|
54
|
+
// Click add/create printer button
|
|
55
|
+
const addBtn = authenticatedPage.locator('button').filter({ hasText: /add|create|new/i }).first();
|
|
56
|
+
await addBtn.click().catch(() => {});
|
|
57
|
+
|
|
58
|
+
// Wait for dialog
|
|
59
|
+
const dialog = createDialogHelper(authenticatedPage);
|
|
60
|
+
await dialog.waitForDialog();
|
|
61
|
+
|
|
62
|
+
await authenticatedPage.waitForTimeout(500);
|
|
63
|
+
|
|
64
|
+
await captureFullPage(authenticatedPage, 'create-printer-dialog.png', 'printer-list');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('04-create-printer-dialog-filled', async ({ authenticatedPage }) => {
|
|
68
|
+
const nav = createNavigationHelper(authenticatedPage);
|
|
69
|
+
await nav.goToPrinterList();
|
|
70
|
+
|
|
71
|
+
await authenticatedPage.waitForTimeout(1000);
|
|
72
|
+
|
|
73
|
+
// Click add printer button
|
|
74
|
+
const addBtn = authenticatedPage.locator('button').filter({ hasText: /add|create|new/i }).first();
|
|
75
|
+
await addBtn.click().catch(() => {});
|
|
76
|
+
|
|
77
|
+
// Wait for dialog
|
|
78
|
+
const dialog = createDialogHelper(authenticatedPage);
|
|
79
|
+
await dialog.waitForDialog();
|
|
80
|
+
|
|
81
|
+
await authenticatedPage.waitForTimeout(500);
|
|
82
|
+
|
|
83
|
+
// Fill form fields
|
|
84
|
+
const inputs = authenticatedPage.locator('.v-dialog input[type="text"]');
|
|
85
|
+
if (await inputs.count() > 0) {
|
|
86
|
+
await inputs.nth(0).fill('Prusa i3 MK4');
|
|
87
|
+
if (await inputs.count() > 1) {
|
|
88
|
+
await inputs.nth(1).fill('http://192.168.1.100');
|
|
89
|
+
}
|
|
90
|
+
if (await inputs.count() > 2) {
|
|
91
|
+
await inputs.nth(2).fill('1234567890ABCDEF');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await authenticatedPage.waitForTimeout(500);
|
|
96
|
+
|
|
97
|
+
await captureFullPage(authenticatedPage, 'create-printer-dialog-filled.png', 'printer-list');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('05-printer-details-panel', async ({ authenticatedPage }) => {
|
|
101
|
+
const nav = createNavigationHelper(authenticatedPage);
|
|
102
|
+
await nav.goToPrinterList();
|
|
103
|
+
|
|
104
|
+
await authenticatedPage.waitForTimeout(1000);
|
|
105
|
+
|
|
106
|
+
// Click on first printer row
|
|
107
|
+
const printerRow = authenticatedPage.locator('tr, .v-list-item, .printer-item').filter({ hasText: /prusa|ender|artillery/i }).first();
|
|
108
|
+
if (await printerRow.isVisible().catch(() => false)) {
|
|
109
|
+
await printerRow.click();
|
|
110
|
+
await authenticatedPage.waitForTimeout(1000);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
await captureFullPage(authenticatedPage, 'printer-details-panel.png', 'printer-list');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('06-attach-floor-to-printer', async ({ authenticatedPage }) => {
|
|
117
|
+
const nav = createNavigationHelper(authenticatedPage);
|
|
118
|
+
await nav.goToPrinterList();
|
|
119
|
+
|
|
120
|
+
await authenticatedPage.waitForTimeout(1000);
|
|
121
|
+
|
|
122
|
+
// Click on first printer to open details
|
|
123
|
+
const printerRow = authenticatedPage.locator('tr, .v-list-item').first();
|
|
124
|
+
if (await printerRow.isVisible().catch(() => false)) {
|
|
125
|
+
await printerRow.click();
|
|
126
|
+
await authenticatedPage.waitForTimeout(500);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Look for floor dropdown or attach button
|
|
130
|
+
const floorSelect = authenticatedPage.locator('select, .v-select, [data-testid="floor-select"]').filter({ hasText: /floor/i }).first();
|
|
131
|
+
if (await floorSelect.isVisible().catch(() => false)) {
|
|
132
|
+
await floorSelect.click();
|
|
133
|
+
await authenticatedPage.waitForTimeout(500);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
await captureFullPage(authenticatedPage, 'attach-floor-to-printer.png', 'printer-list');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('07-attach-camera-to-printer', async ({ authenticatedPage }) => {
|
|
140
|
+
const nav = createNavigationHelper(authenticatedPage);
|
|
141
|
+
await nav.goToPrinterList();
|
|
142
|
+
|
|
143
|
+
await authenticatedPage.waitForTimeout(1000);
|
|
144
|
+
|
|
145
|
+
// Click on first printer
|
|
146
|
+
const printerRow = authenticatedPage.locator('tr, .v-list-item').first();
|
|
147
|
+
if (await printerRow.isVisible().catch(() => false)) {
|
|
148
|
+
await printerRow.click();
|
|
149
|
+
await authenticatedPage.waitForTimeout(500);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Look for camera dropdown or attach button
|
|
153
|
+
const cameraBtn = authenticatedPage.locator('button, .v-select').filter({ hasText: /camera/i }).first();
|
|
154
|
+
if (await cameraBtn.isVisible().catch(() => false)) {
|
|
155
|
+
await cameraBtn.click();
|
|
156
|
+
await authenticatedPage.waitForTimeout(500);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
await captureFullPage(authenticatedPage, 'attach-camera-to-printer.png', 'printer-list');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('08-attach-tag-to-printer', async ({ authenticatedPage }) => {
|
|
163
|
+
const nav = createNavigationHelper(authenticatedPage);
|
|
164
|
+
await nav.goToPrinterList();
|
|
165
|
+
|
|
166
|
+
await authenticatedPage.waitForTimeout(1000);
|
|
167
|
+
|
|
168
|
+
// Click on first printer
|
|
169
|
+
const printerRow = authenticatedPage.locator('tr, .v-list-item').first();
|
|
170
|
+
if (await printerRow.isVisible().catch(() => false)) {
|
|
171
|
+
await printerRow.click();
|
|
172
|
+
await authenticatedPage.waitForTimeout(500);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Look for tag dropdown or attach button
|
|
176
|
+
const tagSelect = authenticatedPage.locator('button, .v-select, [data-testid="tag-select"]').filter({ hasText: /tag/i }).first();
|
|
177
|
+
if (await tagSelect.isVisible().catch(() => false)) {
|
|
178
|
+
await tagSelect.click();
|
|
179
|
+
await authenticatedPage.waitForTimeout(500);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
await captureFullPage(authenticatedPage, 'attach-tag-to-printer.png', 'printer-list');
|
|
183
|
+
});
|
|
184
|
+
});
|