This commit is contained in:
Tomas Dvorak
2026-04-27 09:18:48 +02:00
parent 110a73364b
commit 88c2969a96
14 changed files with 1550 additions and 0 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+17
View File
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Excalidraw FULL</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script type="module" crossorigin src="/assets/index-B3VpcuCP.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Cdp_AjjX.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000">
<rect width="1000" height="1000" rx="200" ry="200" fill="#fff" />
<svg viewBox="0 0 107 101" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2">
<path style="fill:none" d="M24 17h121v121H24z" transform="matrix(.8843 0 0 .83471 -21.223 -14.19)" />
<path d="M119.81 105.98a.549.549 0 0 0-.53-.12c-4.19-6.19-9.52-12.06-14.68-17.73l-.85-.93c0-.11-.05-.21-.12-.3a.548.548 0 0 0-.34-.2l-.17-.18-.12-.09c-.15-.32-.53-.56-.95-.35-1.58.81-3 1.97-4.4 3.04-1.87 1.43-3.7 2.92-5.42 4.52-.7.65-1.39 1.33-1.97 2.09-.28.37-.07.72.27.87-1.22 1.2-2.45 2.45-3.68 3.74-.11.12-.17.28-.16.44.01.16.09.31.22.41l2.16 1.65s.01.03.03.04c3.09 3.05 8.51 7.28 14.25 11.76.85.67 1.71 1.34 2.57 2.01.39.47.76.94 1.12 1.4.19.25.55.3.8.11.13.1.26.21.39.31a.57.57 0 0 0 .8-.1c.07-.09.1-.2.11-.31.04 0 .07.03.1.03.15 0 .31-.06.42-.18l10.18-11.12a.56.56 0 0 0-.04-.8l.01-.01Zm-29.23-3.85c.07.09.14.17.21.25 1.16.98 2.4 2.04 3.66 3.12l-5.12-3.91s-.32-.22-.52-.36c-.11-.08-.21-.16-.31-.24l-.38-.32s.07-.07.1-.11l.35-.35c1.72-1.74 4.67-4.64 6.19-6.06-1.61 1.62-4.87 6.37-4.17 7.98h-.01Zm17.53 13.81-4.22-3.22c-1.65-1.71-3.43-3.4-5.24-5.03 2.28 1.76 4.23 3.25 4.52 3.51 2.21 1.97 2.11 1.61 3.63 2.91l1.83 1.33c-.18.16-.36.33-.53.49l.01.01Zm1.06.81-.08-.06c.16-.13.33-.25.49-.38l-.4.44h-.01ZM42.24 51.45c.14.72.27 1.43.4 2.11.69 3.7 1.33 7.03 2.55 9.56l.48 1.92c.19.73.46 1.64.71 1.83 2.85 2.52 7.22 6.28 11.89 9.82.21.16.5.15.7-.01.01.02.03.03.04.04.11.1.24.15.38.15.16 0 .31-.06.42-.19 5.98-6.65 10.43-12.12 13.6-16.7.2-.25.3-.54.29-.84.2-.24.41-.48.6-.68a.558.558 0 0 0-.1-.86.578.578 0 0 0-.17-.36c-1.39-1.34-2.42-2.31-3.46-3.28-1.84-1.72-3.74-3.5-7.77-7.51-.02-.02-.05-.04-.07-.06a.555.555 0 0 0-.22-.14c-1.11-.39-3.39-.78-6.26-1.28-4.22-.72-10-1.72-15.2-3.27h-.04v-.01s-.02 0-.03.02h-.01l.04-.02s-.31.01-.37.04c-.08.04-.14.09-.19.15-.05.06-.09.12-.47.2-.38.08.08 0 .11 0h-.11v.03c.07.34.05.58.16.97-.02.1.21 1.02.24 1.11l1.83 7.26h.03Zm30.95 6.54s-.03.04-.04.05l-.64-.71c.22.21.44.42.68.66Zm-7.09 9.39s-.07.08-.1.12l-.02-.02c.04-.03.08-.07.13-.1h-.01Zm-7.07 8.47Zm3.02-28.57c.35.35 1.74 1.65 2.06 1.97-1.45-.66-5.06-2.34-6.74-2.88 1.65.29 3.93.66 4.68.91Zm-19.18-2.77c.84 1.44 1.5 6.49 2.16 11.4-.37-1.58-.69-3.12-.99-4.6-.52-2.56-1-4.85-1.67-6.88.14.01.31.03.49.05 0 .01 0 .02.02.03h-.01Zm-.29-1.21c-.23-.02-.44-.04-.62-.05-.02-.04-.03-.08-.04-.12l.66.18v-.01Zm-2.22.45v-.02.02ZM118.9 42.57c.04-.23-1.1-1.24-.74-1.26.85-.04.86-1.35 0-1.31-1.13.06-2.27.32-3.37.53-1.98.37-3.95.78-5.92 1.21-4.39.94-8.77 1.93-13.1 3.11-1.36.37-2.86.7-4.11 1.36-.42.22-.4.67-.17.95-.09.05-.18.08-.28.09-.37.07-.74.13-1.11.19a.566.566 0 0 0-.39.86c-2.32 3.1-4.96 6.44-7.82 9.95-2.81 3.21-5.73 6.63-8.72 10.14-9.41 11.06-20.08 23.6-31.9 34.64-.23.21-.24.57-.03.8.05.06.12.1.19.13-.16.15-.32.3-.48.44-.1.09-.14.2-.16.32-.08.08-.16.17-.23.25-.21.23-.2.59.03.8.23.21.59.2.8-.03.04-.04.08-.09.12-.13a.84.84 0 0 1 1.22 0c.69.74 1.34 1.44 1.95 2.09l-1.38-1.15a.57.57 0 0 0-.8.07c-.2.24-.17.6.07.8l14.82 12.43c.11.09.24.13.37.13.15 0 .29-.06.4-.17l.36-.36a.56.56 0 0 0 .63-.12c20.09-20.18 36.27-35.43 54.8-49.06.17-.12.25-.32.23-.51a.57.57 0 0 0 .48-.39c3.42-10.46 4.08-19.72 4.28-24.27 0-.03.01-.05.02-.07.02-.05.03-.1.04-.14.03-.11.05-.19.05-.19.26-.78.17-1.53-.15-2.15v.02ZM82.98 58.94c.9-1.03 1.79-2.04 2.67-3.02-5.76 7.58-15.3 19.26-28.81 33.14 9.2-10.18 18.47-20.73 26.14-30.12Zm-32.55 52.81-.03-.03c.11.02.19.04.2.04a.47.47 0 0 0-.17 0v-.01Zm6.9 6.42-.05-.04.03-.03c.02 0 .03.02.04.02 0 .02-.02.03-.03.05h.01Zm8.36-7.21 1.38-1.44c.01.01.02.03.03.05-.47.46-.94.93-1.42 1.39h.01Zm2.24-2.21c.26-.3.56-.65.87-1.02.01-.01.02-.03.04-.04 3.29-3.39 6.68-6.82 10.18-10.25.02-.02.05-.04.07-.06.86-.66 1.82-1.39 2.72-2.08-4.52 4.32-9.11 8.78-13.88 13.46v-.01Zm21.65-55.88c-1.86 2.42-3.9 5.56-5.63 8.07-5.46 7.91-23.04 27.28-23.43 27.65-2.71 2.62-10.88 10.46-16.09 15.37-.14.13-.25.24-.34.35a.794.794 0 0 1 .03-1.13c24.82-23.4 39.88-42.89 46-51.38-.13.33-.24.69-.55 1.09l.01-.02Zm16.51 7.1-.01.02c0-.02-.02-.07.01-.02Zm-.91-5.13Zm-5.89 9.45c-2.26-1.31-3.32-3.27-2.71-5.25l.19-.66c.08-.19.17-.38.28-.57.59-.98 1.49-1.85 2.52-2.36.05-.02.1-.03.15-.04a.795.795 0 0 1-.04-.43c.05-.31.25-.58.66-.58.67 0 2.75.62 3.54 1.3.24.19.47.4.68.63.3.35.74.92.96 1.33.13.06.23.62.38.91.14.46.2.93.18 1.4 0 .02 0 .02.01.03-.03.07 0 .37-.04.4-.1.72-.36 1.43-.75 2.05-.04.05-.07.11-.11.16 0 .01-.02.02-.03.04-.3.43-.65.83-1.08 1.13-1.26.89-2.73 1.16-4.2.79a6.33 6.33 0 0 1-.57-.25l-.02-.03Zm16.27-1.63c-.49 2.05-1.09 4.19-1.8 6.38-.03.08-.03.16-.03.23-.1.01-.19.05-.27.11-4.44 3.26-8.73 6.62-12.98 10.11 3.67-3.32 7.39-6.62 11.23-9.95a6.409 6.409 0 0 0 2.11-3.74l.56-3.37.03-.1c.25-.71 1.34-.4 1.17.33h-.02Z" style="fill:#6965db;fill-rule:nonzero" transform="matrix(1 0 0 1 -26.41 -29.49)" />
</svg>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
{
"status": "failed",
"failedTests": [
"c31ff144dc4fee3acd0a-bec551c658216ec9862a",
"c31ff144dc4fee3acd0a-f87315abf5d197970540",
"c31ff144dc4fee3acd0a-fc5e81ebcffdb7687b8e",
"c31ff144dc4fee3acd0a-989f5dcca4211fe0b2e4",
"c31ff144dc4fee3acd0a-ac5aa3cfe7537125a151",
"c31ff144dc4fee3acd0a-7f990aaafdc09c3794e8"
]
}
@@ -0,0 +1,162 @@
# Test info
- Name: dashboard >> shows stats cards
- Location: /home/tdvorak/Desktop/PROG+HTML/Excalidraw/frontend/e2e/app.spec.ts:45:3
# Error details
```
Error: Error reading storage state from playwright/.auth/state.json:
ENOENT: no such file or directory, open 'playwright/.auth/state.json'
```
# Test source
```ts
1 | import { test, expect } from '@playwright/test';
2 |
3 | const BASE = 'http://localhost:3456';
4 |
5 | // Auth: first-run signup, blocked signup, login
6 | test.describe.serial('auth flow', () => {
7 | test.use({ storageState: { cookies: [], origins: [] } });
8 |
9 | test('redirects to signup when no users exist', async ({ page }) => {
10 | await page.goto(BASE + '/');
11 | await expect(page).toHaveURL(/\/signup$/);
12 | await expect(page.getByRole('heading', { name: 'Create account' })).toBeVisible();
13 | });
14 |
15 | test('first user can signup', async ({ page }) => {
16 | await page.goto(BASE + '/signup');
17 | await page.getByLabel('Full Name').fill('E2E User');
18 | await page.getByLabel('Email').fill('e2e@test.com');
19 | await page.getByLabel('Password').fill('e2e-password-123');
20 | await page.getByRole('button', { name: 'Create Account' }).click();
21 | await expect(page).toHaveURL(BASE + '/');
22 | await expect(page.getByText(/Welcome back/)).toBeVisible();
23 | await page.context().storageState({ path: 'playwright/.auth/state.json' });
24 | });
25 |
26 | test('blocks second signup when users exist', async ({ page }) => {
27 | await page.goto(BASE + '/signup');
28 | await expect(page).toHaveURL(/\/login$/);
29 | });
30 |
31 | test('existing user can login', async ({ page }) => {
32 | await page.goto(BASE + '/login');
33 | await page.getByLabel('Email').fill('e2e@test.com');
34 | await page.getByLabel('Password').fill('e2e-password-123');
35 | await page.getByRole('button', { name: 'Sign In' }).click();
36 | await expect(page).toHaveURL(BASE + '/');
37 | await expect(page.getByText(/Welcome back/)).toBeVisible();
38 | });
39 | });
40 |
41 | // Dashboard: quick actions and stats
42 | test.describe.serial('dashboard', () => {
43 | test.use({ storageState: 'playwright/.auth/state.json' });
44 |
> 45 | test('shows stats cards', async ({ page }) => {
| ^ Error: Error reading storage state from playwright/.auth/state.json:
46 | await page.goto(BASE + '/');
47 | await expect(page.getByText('Drawings')).toBeVisible();
48 | await expect(page.getByText('Projects')).toBeVisible();
49 | await expect(page.getByText('Teams')).toBeVisible();
50 | });
51 |
52 | test('quick action: New Project navigates to files', async ({ page }) => {
53 | await page.goto(BASE + '/');
54 | await page.getByRole('button', { name: 'New Project' }).click();
55 | await expect(page).toHaveURL(/\/files/);
56 | await expect(page.getByRole('navigation', { name: 'Project tree' })).toBeVisible();
57 | await expect(page.getByText('All Projects')).toBeVisible();
58 | });
59 |
60 | test('quick action: Invite navigates to team', async ({ page }) => {
61 | await page.goto(BASE + '/');
62 | await page.getByRole('button', { name: 'Invite' }).click();
63 | await expect(page).toHaveURL(/\/team/);
64 | await expect(page.getByRole('heading', { name: 'Team Settings' })).toBeVisible();
65 | });
66 |
67 | test('quick action: Library navigates to marketplace', async ({ page }) => {
68 | await page.goto(BASE + '/');
69 | await page.getByRole('button', { name: 'Library' }).click();
70 | await expect(page).toHaveURL(/\/library/);
71 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible();
72 | });
73 |
74 | test('New Drawing opens template picker', async ({ page }) => {
75 | await page.goto(BASE + '/');
76 | await page.getByRole('button', { name: 'New Drawing' }).click();
77 | await expect(page.getByRole('dialog')).toBeVisible();
78 | await expect(page.getByRole('heading', { name: 'Choose a Template' })).toBeVisible();
79 | await expect(page.getByRole('button', { name: 'Blank Canvas' })).toBeVisible();
80 | await expect(page.getByRole('button', { name: 'To-Do List' })).toBeVisible();
81 | await expect(page.getByRole('button', { name: 'Checklist' })).toBeVisible();
82 | await expect(page.getByRole('button', { name: 'Bullet List' })).toBeVisible();
83 | await expect(page.getByRole('button', { name: 'Flow Chart' })).toBeVisible();
84 | });
85 | });
86 |
87 | // Projects / FileBrowser
88 | test.describe.serial('projects', () => {
89 | test.use({ storageState: 'playwright/.auth/state.json' });
90 |
91 | test('shows Projects label in sidebar and breadcrumb', async ({ page }) => {
92 | await page.goto(BASE + '/files');
93 | await expect(page.getByRole('navigation', { name: 'Main navigation' }).getByText('Projects')).toBeVisible();
94 | await expect(page.getByText('All Projects')).toBeVisible();
95 | });
96 |
97 | test('can create a drawing from file browser', async ({ page }) => {
98 | await page.goto(BASE + '/files');
99 | await page.getByRole('button', { name: 'Create new drawing' }).click();
100 | await expect(page.getByRole('dialog')).toBeVisible();
101 | await page.getByRole('button', { name: 'Blank Canvas' }).click();
102 | await expect(page).toHaveURL(/\/drawing\//);
103 | await expect(page.getByText('Loading Excalidraw')).toBeVisible();
104 | });
105 | });
106 |
107 | // Editor / Canvas
108 | test.describe.serial('editor', () => {
109 | test.use({ storageState: 'playwright/.auth/state.json' });
110 |
111 | test('creates drawing with To-Do template', async ({ page }) => {
112 | await page.goto(BASE + '/');
113 | await page.getByRole('button', { name: 'New Drawing' }).click();
114 | await page.getByRole('button', { name: 'To-Do List' }).click();
115 | await expect(page).toHaveURL(/\/drawing\//);
116 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 });
117 | });
118 |
119 | test('editor shows save controls and back button', async ({ page }) => {
120 | await page.goto(BASE + '/');
121 | await page.getByRole('button', { name: 'New Drawing' }).click();
122 | await page.getByRole('button', { name: 'Blank Canvas' }).click();
123 | await expect(page).toHaveURL(/\/drawing\//);
124 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 });
125 | await expect(page.getByRole('button', { name: /Back/i })).toBeVisible();
126 | });
127 | });
128 |
129 | // Library Marketplace
130 | test.describe.serial('library', () => {
131 | test.use({ storageState: 'playwright/.auth/state.json' });
132 |
133 | test('loads marketplace with search and categories', async ({ page }) => {
134 | await page.goto(BASE + '/library');
135 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible();
136 | await expect(page.getByPlaceholder('Search libraries...')).toBeVisible();
137 | await expect(page.getByRole('button', { name: 'All' }).first()).toBeVisible();
138 | await expect(page.getByRole('button', { name: 'Open External' })).toBeVisible();
139 | });
140 |
141 | test('search filters libraries', async ({ page }) => {
142 | await page.goto(BASE + '/library');
143 | await page.getByPlaceholder('Search libraries...').fill('zzzznonexistent');
144 | await expect(page.getByText('No libraries found')).toBeVisible();
145 | });
```
@@ -0,0 +1,177 @@
# Test info
- Name: editor >> creates drawing with To-Do template
- Location: /home/tdvorak/Desktop/PROG+HTML/Excalidraw/frontend/e2e/app.spec.ts:111:3
# Error details
```
Error: Error reading storage state from playwright/.auth/state.json:
ENOENT: no such file or directory, open 'playwright/.auth/state.json'
```
# Test source
```ts
11 | await expect(page).toHaveURL(/\/signup$/);
12 | await expect(page.getByRole('heading', { name: 'Create account' })).toBeVisible();
13 | });
14 |
15 | test('first user can signup', async ({ page }) => {
16 | await page.goto(BASE + '/signup');
17 | await page.getByLabel('Full Name').fill('E2E User');
18 | await page.getByLabel('Email').fill('e2e@test.com');
19 | await page.getByLabel('Password').fill('e2e-password-123');
20 | await page.getByRole('button', { name: 'Create Account' }).click();
21 | await expect(page).toHaveURL(BASE + '/');
22 | await expect(page.getByText(/Welcome back/)).toBeVisible();
23 | await page.context().storageState({ path: 'playwright/.auth/state.json' });
24 | });
25 |
26 | test('blocks second signup when users exist', async ({ page }) => {
27 | await page.goto(BASE + '/signup');
28 | await expect(page).toHaveURL(/\/login$/);
29 | });
30 |
31 | test('existing user can login', async ({ page }) => {
32 | await page.goto(BASE + '/login');
33 | await page.getByLabel('Email').fill('e2e@test.com');
34 | await page.getByLabel('Password').fill('e2e-password-123');
35 | await page.getByRole('button', { name: 'Sign In' }).click();
36 | await expect(page).toHaveURL(BASE + '/');
37 | await expect(page.getByText(/Welcome back/)).toBeVisible();
38 | });
39 | });
40 |
41 | // Dashboard: quick actions and stats
42 | test.describe.serial('dashboard', () => {
43 | test.use({ storageState: 'playwright/.auth/state.json' });
44 |
45 | test('shows stats cards', async ({ page }) => {
46 | await page.goto(BASE + '/');
47 | await expect(page.getByText('Drawings')).toBeVisible();
48 | await expect(page.getByText('Projects')).toBeVisible();
49 | await expect(page.getByText('Teams')).toBeVisible();
50 | });
51 |
52 | test('quick action: New Project navigates to files', async ({ page }) => {
53 | await page.goto(BASE + '/');
54 | await page.getByRole('button', { name: 'New Project' }).click();
55 | await expect(page).toHaveURL(/\/files/);
56 | await expect(page.getByRole('navigation', { name: 'Project tree' })).toBeVisible();
57 | await expect(page.getByText('All Projects')).toBeVisible();
58 | });
59 |
60 | test('quick action: Invite navigates to team', async ({ page }) => {
61 | await page.goto(BASE + '/');
62 | await page.getByRole('button', { name: 'Invite' }).click();
63 | await expect(page).toHaveURL(/\/team/);
64 | await expect(page.getByRole('heading', { name: 'Team Settings' })).toBeVisible();
65 | });
66 |
67 | test('quick action: Library navigates to marketplace', async ({ page }) => {
68 | await page.goto(BASE + '/');
69 | await page.getByRole('button', { name: 'Library' }).click();
70 | await expect(page).toHaveURL(/\/library/);
71 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible();
72 | });
73 |
74 | test('New Drawing opens template picker', async ({ page }) => {
75 | await page.goto(BASE + '/');
76 | await page.getByRole('button', { name: 'New Drawing' }).click();
77 | await expect(page.getByRole('dialog')).toBeVisible();
78 | await expect(page.getByRole('heading', { name: 'Choose a Template' })).toBeVisible();
79 | await expect(page.getByRole('button', { name: 'Blank Canvas' })).toBeVisible();
80 | await expect(page.getByRole('button', { name: 'To-Do List' })).toBeVisible();
81 | await expect(page.getByRole('button', { name: 'Checklist' })).toBeVisible();
82 | await expect(page.getByRole('button', { name: 'Bullet List' })).toBeVisible();
83 | await expect(page.getByRole('button', { name: 'Flow Chart' })).toBeVisible();
84 | });
85 | });
86 |
87 | // Projects / FileBrowser
88 | test.describe.serial('projects', () => {
89 | test.use({ storageState: 'playwright/.auth/state.json' });
90 |
91 | test('shows Projects label in sidebar and breadcrumb', async ({ page }) => {
92 | await page.goto(BASE + '/files');
93 | await expect(page.getByRole('navigation', { name: 'Main navigation' }).getByText('Projects')).toBeVisible();
94 | await expect(page.getByText('All Projects')).toBeVisible();
95 | });
96 |
97 | test('can create a drawing from file browser', async ({ page }) => {
98 | await page.goto(BASE + '/files');
99 | await page.getByRole('button', { name: 'Create new drawing' }).click();
100 | await expect(page.getByRole('dialog')).toBeVisible();
101 | await page.getByRole('button', { name: 'Blank Canvas' }).click();
102 | await expect(page).toHaveURL(/\/drawing\//);
103 | await expect(page.getByText('Loading Excalidraw')).toBeVisible();
104 | });
105 | });
106 |
107 | // Editor / Canvas
108 | test.describe.serial('editor', () => {
109 | test.use({ storageState: 'playwright/.auth/state.json' });
110 |
> 111 | test('creates drawing with To-Do template', async ({ page }) => {
| ^ Error: Error reading storage state from playwright/.auth/state.json:
112 | await page.goto(BASE + '/');
113 | await page.getByRole('button', { name: 'New Drawing' }).click();
114 | await page.getByRole('button', { name: 'To-Do List' }).click();
115 | await expect(page).toHaveURL(/\/drawing\//);
116 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 });
117 | });
118 |
119 | test('editor shows save controls and back button', async ({ page }) => {
120 | await page.goto(BASE + '/');
121 | await page.getByRole('button', { name: 'New Drawing' }).click();
122 | await page.getByRole('button', { name: 'Blank Canvas' }).click();
123 | await expect(page).toHaveURL(/\/drawing\//);
124 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 });
125 | await expect(page.getByRole('button', { name: /Back/i })).toBeVisible();
126 | });
127 | });
128 |
129 | // Library Marketplace
130 | test.describe.serial('library', () => {
131 | test.use({ storageState: 'playwright/.auth/state.json' });
132 |
133 | test('loads marketplace with search and categories', async ({ page }) => {
134 | await page.goto(BASE + '/library');
135 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible();
136 | await expect(page.getByPlaceholder('Search libraries...')).toBeVisible();
137 | await expect(page.getByRole('button', { name: 'All' }).first()).toBeVisible();
138 | await expect(page.getByRole('button', { name: 'Open External' })).toBeVisible();
139 | });
140 |
141 | test('search filters libraries', async ({ page }) => {
142 | await page.goto(BASE + '/library');
143 | await page.getByPlaceholder('Search libraries...').fill('zzzznonexistent');
144 | await expect(page.getByText('No libraries found')).toBeVisible();
145 | });
146 | });
147 |
148 | // Team / Invites
149 | test.describe.serial('team', () => {
150 | test.use({ storageState: 'playwright/.auth/state.json' });
151 |
152 | test('shows owner in members list', async ({ page }) => {
153 | await page.goto(BASE + '/team');
154 | await expect(page.getByRole('heading', { name: 'Team Settings' })).toBeVisible();
155 | await expect(page.getByText('E2E User')).toBeVisible();
156 | await expect(page.getByText('owner')).toBeVisible();
157 | });
158 |
159 | test('can send team invite', async ({ page }) => {
160 | await page.goto(BASE + '/team');
161 | await page.getByLabel('Email address').fill('invited@test.com');
162 | await page.locator('select').selectOption('editor');
163 | await page.getByRole('button', { name: 'Send Invite' }).click();
164 | await expect(page.getByText('Invite sent!')).toBeVisible();
165 | await expect(page.getByText('Pending Invites')).toBeVisible();
166 | await expect(page.getByText('invited@test.com')).toBeVisible();
167 | await expect(page.getByText('editor').first()).toBeVisible();
168 | });
169 | });
170 |
```
@@ -0,0 +1,155 @@
# Test info
- Name: library >> loads marketplace with search and categories
- Location: /home/tdvorak/Desktop/PROG+HTML/Excalidraw/frontend/e2e/app.spec.ts:133:3
# Error details
```
Error: Error reading storage state from playwright/.auth/state.json:
ENOENT: no such file or directory, open 'playwright/.auth/state.json'
```
# Test source
```ts
33 | await page.getByLabel('Email').fill('e2e@test.com');
34 | await page.getByLabel('Password').fill('e2e-password-123');
35 | await page.getByRole('button', { name: 'Sign In' }).click();
36 | await expect(page).toHaveURL(BASE + '/');
37 | await expect(page.getByText(/Welcome back/)).toBeVisible();
38 | });
39 | });
40 |
41 | // Dashboard: quick actions and stats
42 | test.describe.serial('dashboard', () => {
43 | test.use({ storageState: 'playwright/.auth/state.json' });
44 |
45 | test('shows stats cards', async ({ page }) => {
46 | await page.goto(BASE + '/');
47 | await expect(page.getByText('Drawings')).toBeVisible();
48 | await expect(page.getByText('Projects')).toBeVisible();
49 | await expect(page.getByText('Teams')).toBeVisible();
50 | });
51 |
52 | test('quick action: New Project navigates to files', async ({ page }) => {
53 | await page.goto(BASE + '/');
54 | await page.getByRole('button', { name: 'New Project' }).click();
55 | await expect(page).toHaveURL(/\/files/);
56 | await expect(page.getByRole('navigation', { name: 'Project tree' })).toBeVisible();
57 | await expect(page.getByText('All Projects')).toBeVisible();
58 | });
59 |
60 | test('quick action: Invite navigates to team', async ({ page }) => {
61 | await page.goto(BASE + '/');
62 | await page.getByRole('button', { name: 'Invite' }).click();
63 | await expect(page).toHaveURL(/\/team/);
64 | await expect(page.getByRole('heading', { name: 'Team Settings' })).toBeVisible();
65 | });
66 |
67 | test('quick action: Library navigates to marketplace', async ({ page }) => {
68 | await page.goto(BASE + '/');
69 | await page.getByRole('button', { name: 'Library' }).click();
70 | await expect(page).toHaveURL(/\/library/);
71 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible();
72 | });
73 |
74 | test('New Drawing opens template picker', async ({ page }) => {
75 | await page.goto(BASE + '/');
76 | await page.getByRole('button', { name: 'New Drawing' }).click();
77 | await expect(page.getByRole('dialog')).toBeVisible();
78 | await expect(page.getByRole('heading', { name: 'Choose a Template' })).toBeVisible();
79 | await expect(page.getByRole('button', { name: 'Blank Canvas' })).toBeVisible();
80 | await expect(page.getByRole('button', { name: 'To-Do List' })).toBeVisible();
81 | await expect(page.getByRole('button', { name: 'Checklist' })).toBeVisible();
82 | await expect(page.getByRole('button', { name: 'Bullet List' })).toBeVisible();
83 | await expect(page.getByRole('button', { name: 'Flow Chart' })).toBeVisible();
84 | });
85 | });
86 |
87 | // Projects / FileBrowser
88 | test.describe.serial('projects', () => {
89 | test.use({ storageState: 'playwright/.auth/state.json' });
90 |
91 | test('shows Projects label in sidebar and breadcrumb', async ({ page }) => {
92 | await page.goto(BASE + '/files');
93 | await expect(page.getByRole('navigation', { name: 'Main navigation' }).getByText('Projects')).toBeVisible();
94 | await expect(page.getByText('All Projects')).toBeVisible();
95 | });
96 |
97 | test('can create a drawing from file browser', async ({ page }) => {
98 | await page.goto(BASE + '/files');
99 | await page.getByRole('button', { name: 'Create new drawing' }).click();
100 | await expect(page.getByRole('dialog')).toBeVisible();
101 | await page.getByRole('button', { name: 'Blank Canvas' }).click();
102 | await expect(page).toHaveURL(/\/drawing\//);
103 | await expect(page.getByText('Loading Excalidraw')).toBeVisible();
104 | });
105 | });
106 |
107 | // Editor / Canvas
108 | test.describe.serial('editor', () => {
109 | test.use({ storageState: 'playwright/.auth/state.json' });
110 |
111 | test('creates drawing with To-Do template', async ({ page }) => {
112 | await page.goto(BASE + '/');
113 | await page.getByRole('button', { name: 'New Drawing' }).click();
114 | await page.getByRole('button', { name: 'To-Do List' }).click();
115 | await expect(page).toHaveURL(/\/drawing\//);
116 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 });
117 | });
118 |
119 | test('editor shows save controls and back button', async ({ page }) => {
120 | await page.goto(BASE + '/');
121 | await page.getByRole('button', { name: 'New Drawing' }).click();
122 | await page.getByRole('button', { name: 'Blank Canvas' }).click();
123 | await expect(page).toHaveURL(/\/drawing\//);
124 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 });
125 | await expect(page.getByRole('button', { name: /Back/i })).toBeVisible();
126 | });
127 | });
128 |
129 | // Library Marketplace
130 | test.describe.serial('library', () => {
131 | test.use({ storageState: 'playwright/.auth/state.json' });
132 |
> 133 | test('loads marketplace with search and categories', async ({ page }) => {
| ^ Error: Error reading storage state from playwright/.auth/state.json:
134 | await page.goto(BASE + '/library');
135 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible();
136 | await expect(page.getByPlaceholder('Search libraries...')).toBeVisible();
137 | await expect(page.getByRole('button', { name: 'All' }).first()).toBeVisible();
138 | await expect(page.getByRole('button', { name: 'Open External' })).toBeVisible();
139 | });
140 |
141 | test('search filters libraries', async ({ page }) => {
142 | await page.goto(BASE + '/library');
143 | await page.getByPlaceholder('Search libraries...').fill('zzzznonexistent');
144 | await expect(page.getByText('No libraries found')).toBeVisible();
145 | });
146 | });
147 |
148 | // Team / Invites
149 | test.describe.serial('team', () => {
150 | test.use({ storageState: 'playwright/.auth/state.json' });
151 |
152 | test('shows owner in members list', async ({ page }) => {
153 | await page.goto(BASE + '/team');
154 | await expect(page.getByRole('heading', { name: 'Team Settings' })).toBeVisible();
155 | await expect(page.getByText('E2E User')).toBeVisible();
156 | await expect(page.getByText('owner')).toBeVisible();
157 | });
158 |
159 | test('can send team invite', async ({ page }) => {
160 | await page.goto(BASE + '/team');
161 | await page.getByLabel('Email address').fill('invited@test.com');
162 | await page.locator('select').selectOption('editor');
163 | await page.getByRole('button', { name: 'Send Invite' }).click();
164 | await expect(page.getByText('Invite sent!')).toBeVisible();
165 | await expect(page.getByText('Pending Invites')).toBeVisible();
166 | await expect(page.getByText('invited@test.com')).toBeVisible();
167 | await expect(page.getByText('editor').first()).toBeVisible();
168 | });
169 | });
170 |
```
@@ -0,0 +1,187 @@
# Test info
- Name: projects >> shows Projects label in sidebar and breadcrumb
- Location: /home/tdvorak/Desktop/PROG+HTML/Excalidraw/frontend/e2e/app.spec.ts:91:3
# Error details
```
Error: Error reading storage state from playwright/.auth/state.json:
ENOENT: no such file or directory, open 'playwright/.auth/state.json'
```
# Test source
```ts
1 | import { test, expect } from '@playwright/test';
2 |
3 | const BASE = 'http://localhost:3456';
4 |
5 | // Auth: first-run signup, blocked signup, login
6 | test.describe.serial('auth flow', () => {
7 | test.use({ storageState: { cookies: [], origins: [] } });
8 |
9 | test('redirects to signup when no users exist', async ({ page }) => {
10 | await page.goto(BASE + '/');
11 | await expect(page).toHaveURL(/\/signup$/);
12 | await expect(page.getByRole('heading', { name: 'Create account' })).toBeVisible();
13 | });
14 |
15 | test('first user can signup', async ({ page }) => {
16 | await page.goto(BASE + '/signup');
17 | await page.getByLabel('Full Name').fill('E2E User');
18 | await page.getByLabel('Email').fill('e2e@test.com');
19 | await page.getByLabel('Password').fill('e2e-password-123');
20 | await page.getByRole('button', { name: 'Create Account' }).click();
21 | await expect(page).toHaveURL(BASE + '/');
22 | await expect(page.getByText(/Welcome back/)).toBeVisible();
23 | await page.context().storageState({ path: 'playwright/.auth/state.json' });
24 | });
25 |
26 | test('blocks second signup when users exist', async ({ page }) => {
27 | await page.goto(BASE + '/signup');
28 | await expect(page).toHaveURL(/\/login$/);
29 | });
30 |
31 | test('existing user can login', async ({ page }) => {
32 | await page.goto(BASE + '/login');
33 | await page.getByLabel('Email').fill('e2e@test.com');
34 | await page.getByLabel('Password').fill('e2e-password-123');
35 | await page.getByRole('button', { name: 'Sign In' }).click();
36 | await expect(page).toHaveURL(BASE + '/');
37 | await expect(page.getByText(/Welcome back/)).toBeVisible();
38 | });
39 | });
40 |
41 | // Dashboard: quick actions and stats
42 | test.describe.serial('dashboard', () => {
43 | test.use({ storageState: 'playwright/.auth/state.json' });
44 |
45 | test('shows stats cards', async ({ page }) => {
46 | await page.goto(BASE + '/');
47 | await expect(page.getByText('Drawings')).toBeVisible();
48 | await expect(page.getByText('Projects')).toBeVisible();
49 | await expect(page.getByText('Teams')).toBeVisible();
50 | });
51 |
52 | test('quick action: New Project navigates to files', async ({ page }) => {
53 | await page.goto(BASE + '/');
54 | await page.getByRole('button', { name: 'New Project' }).click();
55 | await expect(page).toHaveURL(/\/files/);
56 | await expect(page.getByRole('navigation', { name: 'Project tree' })).toBeVisible();
57 | await expect(page.getByText('All Projects')).toBeVisible();
58 | });
59 |
60 | test('quick action: Invite navigates to team', async ({ page }) => {
61 | await page.goto(BASE + '/');
62 | await page.getByRole('button', { name: 'Invite' }).click();
63 | await expect(page).toHaveURL(/\/team/);
64 | await expect(page.getByRole('heading', { name: 'Team Settings' })).toBeVisible();
65 | });
66 |
67 | test('quick action: Library navigates to marketplace', async ({ page }) => {
68 | await page.goto(BASE + '/');
69 | await page.getByRole('button', { name: 'Library' }).click();
70 | await expect(page).toHaveURL(/\/library/);
71 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible();
72 | });
73 |
74 | test('New Drawing opens template picker', async ({ page }) => {
75 | await page.goto(BASE + '/');
76 | await page.getByRole('button', { name: 'New Drawing' }).click();
77 | await expect(page.getByRole('dialog')).toBeVisible();
78 | await expect(page.getByRole('heading', { name: 'Choose a Template' })).toBeVisible();
79 | await expect(page.getByRole('button', { name: 'Blank Canvas' })).toBeVisible();
80 | await expect(page.getByRole('button', { name: 'To-Do List' })).toBeVisible();
81 | await expect(page.getByRole('button', { name: 'Checklist' })).toBeVisible();
82 | await expect(page.getByRole('button', { name: 'Bullet List' })).toBeVisible();
83 | await expect(page.getByRole('button', { name: 'Flow Chart' })).toBeVisible();
84 | });
85 | });
86 |
87 | // Projects / FileBrowser
88 | test.describe.serial('projects', () => {
89 | test.use({ storageState: 'playwright/.auth/state.json' });
90 |
> 91 | test('shows Projects label in sidebar and breadcrumb', async ({ page }) => {
| ^ Error: Error reading storage state from playwright/.auth/state.json:
92 | await page.goto(BASE + '/files');
93 | await expect(page.getByRole('navigation', { name: 'Main navigation' }).getByText('Projects')).toBeVisible();
94 | await expect(page.getByText('All Projects')).toBeVisible();
95 | });
96 |
97 | test('can create a drawing from file browser', async ({ page }) => {
98 | await page.goto(BASE + '/files');
99 | await page.getByRole('button', { name: 'Create new drawing' }).click();
100 | await expect(page.getByRole('dialog')).toBeVisible();
101 | await page.getByRole('button', { name: 'Blank Canvas' }).click();
102 | await expect(page).toHaveURL(/\/drawing\//);
103 | await expect(page.getByText('Loading Excalidraw')).toBeVisible();
104 | });
105 | });
106 |
107 | // Editor / Canvas
108 | test.describe.serial('editor', () => {
109 | test.use({ storageState: 'playwright/.auth/state.json' });
110 |
111 | test('creates drawing with To-Do template', async ({ page }) => {
112 | await page.goto(BASE + '/');
113 | await page.getByRole('button', { name: 'New Drawing' }).click();
114 | await page.getByRole('button', { name: 'To-Do List' }).click();
115 | await expect(page).toHaveURL(/\/drawing\//);
116 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 });
117 | });
118 |
119 | test('editor shows save controls and back button', async ({ page }) => {
120 | await page.goto(BASE + '/');
121 | await page.getByRole('button', { name: 'New Drawing' }).click();
122 | await page.getByRole('button', { name: 'Blank Canvas' }).click();
123 | await expect(page).toHaveURL(/\/drawing\//);
124 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 });
125 | await expect(page.getByRole('button', { name: /Back/i })).toBeVisible();
126 | });
127 | });
128 |
129 | // Library Marketplace
130 | test.describe.serial('library', () => {
131 | test.use({ storageState: 'playwright/.auth/state.json' });
132 |
133 | test('loads marketplace with search and categories', async ({ page }) => {
134 | await page.goto(BASE + '/library');
135 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible();
136 | await expect(page.getByPlaceholder('Search libraries...')).toBeVisible();
137 | await expect(page.getByRole('button', { name: 'All' }).first()).toBeVisible();
138 | await expect(page.getByRole('button', { name: 'Open External' })).toBeVisible();
139 | });
140 |
141 | test('search filters libraries', async ({ page }) => {
142 | await page.goto(BASE + '/library');
143 | await page.getByPlaceholder('Search libraries...').fill('zzzznonexistent');
144 | await expect(page.getByText('No libraries found')).toBeVisible();
145 | });
146 | });
147 |
148 | // Team / Invites
149 | test.describe.serial('team', () => {
150 | test.use({ storageState: 'playwright/.auth/state.json' });
151 |
152 | test('shows owner in members list', async ({ page }) => {
153 | await page.goto(BASE + '/team');
154 | await expect(page.getByRole('heading', { name: 'Team Settings' })).toBeVisible();
155 | await expect(page.getByText('E2E User')).toBeVisible();
156 | await expect(page.getByText('owner')).toBeVisible();
157 | });
158 |
159 | test('can send team invite', async ({ page }) => {
160 | await page.goto(BASE + '/team');
161 | await page.getByLabel('Email address').fill('invited@test.com');
162 | await page.locator('select').selectOption('editor');
163 | await page.getByRole('button', { name: 'Send Invite' }).click();
164 | await expect(page.getByText('Invite sent!')).toBeVisible();
165 | await expect(page.getByText('Pending Invites')).toBeVisible();
166 | await expect(page.getByText('invited@test.com')).toBeVisible();
167 | await expect(page.getByText('editor').first()).toBeVisible();
168 | });
169 | });
170 |
```
@@ -0,0 +1,136 @@
# Test info
- Name: team >> shows owner in members list
- Location: /home/tdvorak/Desktop/PROG+HTML/Excalidraw/frontend/e2e/app.spec.ts:152:3
# Error details
```
Error: Error reading storage state from playwright/.auth/state.json:
ENOENT: no such file or directory, open 'playwright/.auth/state.json'
```
# Test source
```ts
52 | test('quick action: New Project navigates to files', async ({ page }) => {
53 | await page.goto(BASE + '/');
54 | await page.getByRole('button', { name: 'New Project' }).click();
55 | await expect(page).toHaveURL(/\/files/);
56 | await expect(page.getByRole('navigation', { name: 'Project tree' })).toBeVisible();
57 | await expect(page.getByText('All Projects')).toBeVisible();
58 | });
59 |
60 | test('quick action: Invite navigates to team', async ({ page }) => {
61 | await page.goto(BASE + '/');
62 | await page.getByRole('button', { name: 'Invite' }).click();
63 | await expect(page).toHaveURL(/\/team/);
64 | await expect(page.getByRole('heading', { name: 'Team Settings' })).toBeVisible();
65 | });
66 |
67 | test('quick action: Library navigates to marketplace', async ({ page }) => {
68 | await page.goto(BASE + '/');
69 | await page.getByRole('button', { name: 'Library' }).click();
70 | await expect(page).toHaveURL(/\/library/);
71 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible();
72 | });
73 |
74 | test('New Drawing opens template picker', async ({ page }) => {
75 | await page.goto(BASE + '/');
76 | await page.getByRole('button', { name: 'New Drawing' }).click();
77 | await expect(page.getByRole('dialog')).toBeVisible();
78 | await expect(page.getByRole('heading', { name: 'Choose a Template' })).toBeVisible();
79 | await expect(page.getByRole('button', { name: 'Blank Canvas' })).toBeVisible();
80 | await expect(page.getByRole('button', { name: 'To-Do List' })).toBeVisible();
81 | await expect(page.getByRole('button', { name: 'Checklist' })).toBeVisible();
82 | await expect(page.getByRole('button', { name: 'Bullet List' })).toBeVisible();
83 | await expect(page.getByRole('button', { name: 'Flow Chart' })).toBeVisible();
84 | });
85 | });
86 |
87 | // Projects / FileBrowser
88 | test.describe.serial('projects', () => {
89 | test.use({ storageState: 'playwright/.auth/state.json' });
90 |
91 | test('shows Projects label in sidebar and breadcrumb', async ({ page }) => {
92 | await page.goto(BASE + '/files');
93 | await expect(page.getByRole('navigation', { name: 'Main navigation' }).getByText('Projects')).toBeVisible();
94 | await expect(page.getByText('All Projects')).toBeVisible();
95 | });
96 |
97 | test('can create a drawing from file browser', async ({ page }) => {
98 | await page.goto(BASE + '/files');
99 | await page.getByRole('button', { name: 'Create new drawing' }).click();
100 | await expect(page.getByRole('dialog')).toBeVisible();
101 | await page.getByRole('button', { name: 'Blank Canvas' }).click();
102 | await expect(page).toHaveURL(/\/drawing\//);
103 | await expect(page.getByText('Loading Excalidraw')).toBeVisible();
104 | });
105 | });
106 |
107 | // Editor / Canvas
108 | test.describe.serial('editor', () => {
109 | test.use({ storageState: 'playwright/.auth/state.json' });
110 |
111 | test('creates drawing with To-Do template', async ({ page }) => {
112 | await page.goto(BASE + '/');
113 | await page.getByRole('button', { name: 'New Drawing' }).click();
114 | await page.getByRole('button', { name: 'To-Do List' }).click();
115 | await expect(page).toHaveURL(/\/drawing\//);
116 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 });
117 | });
118 |
119 | test('editor shows save controls and back button', async ({ page }) => {
120 | await page.goto(BASE + '/');
121 | await page.getByRole('button', { name: 'New Drawing' }).click();
122 | await page.getByRole('button', { name: 'Blank Canvas' }).click();
123 | await expect(page).toHaveURL(/\/drawing\//);
124 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 });
125 | await expect(page.getByRole('button', { name: /Back/i })).toBeVisible();
126 | });
127 | });
128 |
129 | // Library Marketplace
130 | test.describe.serial('library', () => {
131 | test.use({ storageState: 'playwright/.auth/state.json' });
132 |
133 | test('loads marketplace with search and categories', async ({ page }) => {
134 | await page.goto(BASE + '/library');
135 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible();
136 | await expect(page.getByPlaceholder('Search libraries...')).toBeVisible();
137 | await expect(page.getByRole('button', { name: 'All' }).first()).toBeVisible();
138 | await expect(page.getByRole('button', { name: 'Open External' })).toBeVisible();
139 | });
140 |
141 | test('search filters libraries', async ({ page }) => {
142 | await page.goto(BASE + '/library');
143 | await page.getByPlaceholder('Search libraries...').fill('zzzznonexistent');
144 | await expect(page.getByText('No libraries found')).toBeVisible();
145 | });
146 | });
147 |
148 | // Team / Invites
149 | test.describe.serial('team', () => {
150 | test.use({ storageState: 'playwright/.auth/state.json' });
151 |
> 152 | test('shows owner in members list', async ({ page }) => {
| ^ Error: Error reading storage state from playwright/.auth/state.json:
153 | await page.goto(BASE + '/team');
154 | await expect(page.getByRole('heading', { name: 'Team Settings' })).toBeVisible();
155 | await expect(page.getByText('E2E User')).toBeVisible();
156 | await expect(page.getByText('owner')).toBeVisible();
157 | });
158 |
159 | test('can send team invite', async ({ page }) => {
160 | await page.goto(BASE + '/team');
161 | await page.getByLabel('Email address').fill('invited@test.com');
162 | await page.locator('select').selectOption('editor');
163 | await page.getByRole('button', { name: 'Send Invite' }).click();
164 | await expect(page.getByText('Invite sent!')).toBeVisible();
165 | await expect(page.getByText('Pending Invites')).toBeVisible();
166 | await expect(page.getByText('invited@test.com')).toBeVisible();
167 | await expect(page.getByText('editor').first()).toBeVisible();
168 | });
169 | });
170 |
```