@fdm-monster/client-next 2.2.2 → 2.2.4

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.
Files changed (40) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/README.md +19 -0
  3. package/RELEASE_NOTES.MD +21 -0
  4. package/dist/assets/index-CvbkNANW.js +105 -0
  5. package/dist/assets/index-CvbkNANW.js.map +1 -0
  6. package/dist/assets/index-DfA7W6iO.css +1 -0
  7. package/dist/index.html +3 -3
  8. package/package.json +21 -2
  9. package/screenshots/COVERAGE.md +383 -0
  10. package/screenshots/README.md +431 -0
  11. package/screenshots/fixtures/api-mock.ts +699 -0
  12. package/screenshots/fixtures/data/auth.fixtures.ts +79 -0
  13. package/screenshots/fixtures/data/cameras.fixtures.ts +48 -0
  14. package/screenshots/fixtures/data/files.fixtures.ts +56 -0
  15. package/screenshots/fixtures/data/floors.fixtures.ts +39 -0
  16. package/screenshots/fixtures/data/jobs.fixtures.ts +172 -0
  17. package/screenshots/fixtures/data/printers.fixtures.ts +132 -0
  18. package/screenshots/fixtures/data/settings.fixtures.ts +62 -0
  19. package/screenshots/fixtures/socketio-mock.ts +76 -0
  20. package/screenshots/fixtures/test-fixtures.ts +112 -0
  21. package/screenshots/helpers/dialog.helper.ts +196 -0
  22. package/screenshots/helpers/form.helper.ts +207 -0
  23. package/screenshots/helpers/navigation.helper.ts +191 -0
  24. package/screenshots/playwright.screenshots.config.ts +70 -0
  25. package/screenshots/suites/00-example.screenshots.spec.ts +29 -0
  26. package/screenshots/suites/01-auth.screenshots.spec.ts +130 -0
  27. package/screenshots/suites/02-dashboard.screenshots.spec.ts +106 -0
  28. package/screenshots/suites/03-printer-grid.screenshots.spec.ts +160 -0
  29. package/screenshots/suites/04-printer-list.screenshots.spec.ts +184 -0
  30. package/screenshots/suites/05-camera-grid.screenshots.spec.ts +127 -0
  31. package/screenshots/suites/06-print-jobs.screenshots.spec.ts +139 -0
  32. package/screenshots/suites/07-queue.screenshots.spec.ts +86 -0
  33. package/screenshots/suites/08-files.screenshots.spec.ts +142 -0
  34. package/screenshots/suites/09-settings.screenshots.spec.ts +130 -0
  35. package/screenshots/suites/10-panels-dialogs.screenshots.spec.ts +245 -0
  36. package/screenshots/utils.ts +216 -0
  37. package/vitest.config.ts +8 -0
  38. package/dist/assets/index-BlOaSQti.js +0 -105
  39. package/dist/assets/index-BlOaSQti.js.map +0 -1
  40. 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
+ });