wuyiqunLu commited on
Commit
06bd1d1
1 Parent(s): 466a263

feat: add playwright e2e test for chat page with new and old data structure (#99)

Browse files

You can run npx playwright test --ui locally to check the test

<img width="1582" alt="image"
src="https://github.com/landing-ai/vision-agent-ui/assets/132986242/3f6d2db5-c2d4-4190-92e9-b152894788bc">

<img width="1264" alt="image"
src="https://github.com/landing-ai/vision-agent-ui/assets/132986242/2429efe1-56e0-4f3f-9694-599475097f83">

.github/workflows/playwright.yml ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Playwright Tests
2
+ on:
3
+ deployment_status:
4
+ jobs:
5
+ run-e2es:
6
+ timeout-minutes: 5
7
+ runs-on: ubuntu-latest
8
+ if: github.event_name == 'deployment_status' && github.event.deployment_status.state == 'success'
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+ - name: Setup pnpm
12
+ uses: pnpm/action-setup@v4
13
+ with:
14
+ version: 9.1.1
15
+ - name: Use Node.js ${{ matrix.node-version }}
16
+ uses: actions/setup-node@v4
17
+ with:
18
+ node-version: ${{ matrix.node-version }}
19
+ cache: 'pnpm'
20
+ - name: Install dependencies
21
+ run: pnpm i --frozen-lockfile
22
+ - name: Install Playwright Browsers
23
+ run: pnpm exec playwright install --with-deps
24
+ - name: Run Playwright tests
25
+ run: pnpm exec playwright test
26
+ env:
27
+ BASE_URL: ${{ github.event.deployment_status.environment_url }}
28
+ VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}
29
+ - uses: actions/upload-artifact@v4
30
+ if: always()
31
+ with:
32
+ name: playwright-report
33
+ path: playwright-report/
34
+ retention-days: 7
.gitignore CHANGED
@@ -39,4 +39,8 @@ yarn-error.log*
39
 
40
  # ts
41
  tsconfig.tsbuildinfo
42
- certificates
 
 
 
 
 
39
 
40
  # ts
41
  tsconfig.tsbuildinfo
42
+ certificates
43
+ /test-results/
44
+ /playwright-report/
45
+ /blob-report/
46
+ /playwright/.cache/
app/page.tsx CHANGED
@@ -2,29 +2,13 @@
2
 
3
  import { useRouter } from 'next/navigation';
4
 
5
- import { useRef, useState } from 'react';
6
  import Composer, { ComposerRef } from '@/components/chat/Composer';
7
  import { dbPostCreateChat } from '@/lib/db/functions';
8
  import { nanoid } from '@/lib/utils';
9
  import Chip from '@/components/ui/Chip';
10
  import { IconArrowUpRight } from '@/components/ui/Icons';
11
-
12
- const EXAMPLES = [
13
- {
14
- title: 'Counting flowers in image',
15
- mediaUrl:
16
- 'https://vision-agent-dev.s3.us-east-2.amazonaws.com/examples/flower.png',
17
- prompt:
18
- 'Detect flowers in this image, draw boxes and output the image, also return total number of flowers',
19
- },
20
- {
21
- title: 'Detecting sharks in video',
22
- mediaUrl:
23
- 'https://vision-agent-dev.s3.us-east-2.amazonaws.com/examples/shark3_short.mp4',
24
- prompt:
25
- 'Can you detect any surfboards or sharks in the video, draw a green line between the shark and the nearest surfboard and add the distance between them in meters assuming 30 pixels is 1 meter. Make the line red if the shark is within 10 meters of a surfboard. Sample the video at 1 frames per second and save the output video as output.mp4.',
26
- },
27
- ];
28
 
29
  export default function Page() {
30
  const router = useRouter();
 
2
 
3
  import { useRouter } from 'next/navigation';
4
 
5
+ import { useRef } from 'react';
6
  import Composer, { ComposerRef } from '@/components/chat/Composer';
7
  import { dbPostCreateChat } from '@/lib/db/functions';
8
  import { nanoid } from '@/lib/utils';
9
  import Chip from '@/components/ui/Chip';
10
  import { IconArrowUpRight } from '@/components/ui/Icons';
11
+ import { EXAMPLES } from '@/lib/constants';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  export default function Page() {
14
  const router = useRouter();
components/CodeResultDisplay.tsx CHANGED
@@ -53,13 +53,21 @@ const CodeResultDisplay: React.FC<{
53
  const finalResult = results?.find(_ => _.is_main_result)?.text;
54
 
55
  return (
56
- <div className="rounded-lg overflow-hidden relative max-w-5xl">
 
 
 
57
  <CodeBlock language="python" value={code} />
58
  <div className="rounded-lg relative">
59
  <div className="absolute left-1/2 -translate-x-1/2 -top-4 z-10">
60
  <Dialog>
61
  <DialogTrigger asChild>
62
- <Button variant="ghost" size="icon" className="size-8">
 
 
 
 
 
63
  <IconTerminalWindow className="text-teal-500 size-4" />
64
  </Button>
65
  </DialogTrigger>
@@ -111,7 +119,11 @@ const CodeResultDisplay: React.FC<{
111
  <p>image output</p>
112
  <Dialog>
113
  <DialogTrigger asChild>
114
- <Button variant="outline" size="sm">
 
 
 
 
115
  View all
116
  </Button>
117
  </DialogTrigger>
@@ -120,7 +132,11 @@ const CodeResultDisplay: React.FC<{
120
  <CarouselContent>
121
  {imageResults.map((png, index) => (
122
  <CarouselItem key={'png' + index}>
123
- <Img src={png!} width={1200} alt="result-image" />
 
 
 
 
124
  </CarouselItem>
125
  ))}
126
  </CarouselContent>
@@ -136,7 +152,7 @@ const CodeResultDisplay: React.FC<{
136
  key={'png' + index}
137
  src={png!}
138
  width={200}
139
- alt="result-image"
140
  />
141
  ))}
142
  </div>
 
53
  const finalResult = results?.find(_ => _.is_main_result)?.text;
54
 
55
  return (
56
+ <div
57
+ className="rounded-lg overflow-hidden relative max-w-5xl"
58
+ data-testid="code-result-display-container"
59
+ >
60
  <CodeBlock language="python" value={code} />
61
  <div className="rounded-lg relative">
62
  <div className="absolute left-1/2 -translate-x-1/2 -top-4 z-10">
63
  <Dialog>
64
  <DialogTrigger asChild>
65
+ <Button
66
+ data-testid="open-final-test-code-dialog-button"
67
+ variant="ghost"
68
+ size="icon"
69
+ className="size-8"
70
+ >
71
  <IconTerminalWindow className="text-teal-500 size-4" />
72
  </Button>
73
  </DialogTrigger>
 
119
  <p>image output</p>
120
  <Dialog>
121
  <DialogTrigger asChild>
122
+ <Button
123
+ variant="outline"
124
+ size="sm"
125
+ data-testid="view-all-result-images-button"
126
+ >
127
  View all
128
  </Button>
129
  </DialogTrigger>
 
132
  <CarouselContent>
133
  {imageResults.map((png, index) => (
134
  <CarouselItem key={'png' + index}>
135
+ <Img
136
+ src={png!}
137
+ width={1200}
138
+ alt={`detail-result-image-${index}`}
139
+ />
140
  </CarouselItem>
141
  ))}
142
  </CarouselContent>
 
152
  key={'png' + index}
153
  src={png!}
154
  width={200}
155
+ alt={`result-image-${index}`}
156
  />
157
  ))}
158
  </div>
components/chat/ChatMessage.tsx CHANGED
@@ -29,6 +29,14 @@ import { Message } from '@prisma/client';
29
  import { Separator } from '../ui/Separator';
30
  import { cn } from '@/lib/utils';
31
  import toast from 'react-hot-toast';
 
 
 
 
 
 
 
 
32
 
33
  export interface ChatMessageProps {
34
  message: Message;
@@ -218,16 +226,42 @@ const ChunkTypeToText: React.FC<{
218
  ? `(${Math.round(displayMs / 100) / 10}s)`
219
  : '';
220
 
221
- if (type === 'plans') return <p>Creating instructions {durationDisplay}</p>;
222
- if (type === 'tools') return <p>Retrieving tools {durationDisplay}</p>;
 
 
 
 
 
 
 
 
 
 
223
  if (type === 'code' && status === 'started')
224
- return <p>Generating code {durationDisplay}</p>;
 
 
 
 
225
  if (type === 'code' && status === 'running')
226
- return <p>Executing code {durationDisplay}</p>;
 
 
 
 
227
  if (type === 'code' && status === 'completed')
228
- return <p>Code execution success {durationDisplay}</p>;
 
 
 
 
229
  if (type === 'code' && status === 'failed')
230
- return <p>Code execution failure {durationDisplay}</p>;
 
 
 
 
231
 
232
  return null;
233
  };
 
29
  import { Separator } from '../ui/Separator';
30
  import { cn } from '@/lib/utils';
31
  import toast from 'react-hot-toast';
32
+ import {
33
+ EXECUTE_CODE_FAILURE_TITLE,
34
+ EXECUTE_CODE_SUCCESS_TITLE,
35
+ EXECUTE_CODE_TITLE,
36
+ GENERATE_CODE_TITLE,
37
+ PLAN_TITLE,
38
+ TOOLS_TITLE,
39
+ } from '@/lib/constants';
40
 
41
  export interface ChatMessageProps {
42
  message: Message;
 
226
  ? `(${Math.round(displayMs / 100) / 10}s)`
227
  : '';
228
 
229
+ if (type === 'plans')
230
+ return (
231
+ <p>
232
+ {PLAN_TITLE} {durationDisplay}
233
+ </p>
234
+ );
235
+ if (type === 'tools')
236
+ return (
237
+ <p>
238
+ {TOOLS_TITLE} {durationDisplay}
239
+ </p>
240
+ );
241
  if (type === 'code' && status === 'started')
242
+ return (
243
+ <p>
244
+ {GENERATE_CODE_TITLE} {durationDisplay}
245
+ </p>
246
+ );
247
  if (type === 'code' && status === 'running')
248
+ return (
249
+ <p>
250
+ {EXECUTE_CODE_TITLE} {durationDisplay}
251
+ </p>
252
+ );
253
  if (type === 'code' && status === 'completed')
254
+ return (
255
+ <p>
256
+ {EXECUTE_CODE_SUCCESS_TITLE} {durationDisplay}
257
+ </p>
258
+ );
259
  if (type === 'code' && status === 'failed')
260
+ return (
261
+ <p>
262
+ {EXECUTE_CODE_FAILURE_TITLE} {durationDisplay}
263
+ </p>
264
+ );
265
 
266
  return null;
267
  };
components/ui/Dialog.tsx CHANGED
@@ -38,9 +38,10 @@ const DialogContent: React.ForwardRefExoticComponent<
38
  <DialogPrimitive.Content
39
  ref={ref}
40
  className={cn(
41
- 'fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg max-h-[1000px] overflow-auto -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
42
  className,
43
  )}
 
44
  {...props}
45
  >
46
  {children}
 
38
  <DialogPrimitive.Content
39
  ref={ref}
40
  className={cn(
41
+ 'fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg overflow-auto -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
42
  className,
43
  )}
44
+ style={{ maxHeight: '100vh' }}
45
  {...props}
46
  >
47
  {children}
lib/constants.ts CHANGED
@@ -1 +1,23 @@
1
- export const CLEANED_SEPARATOR = '|CLEANED|';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const EXAMPLES = [
2
+ {
3
+ title: 'Counting flowers in image',
4
+ mediaUrl:
5
+ 'https://vision-agent-dev.s3.us-east-2.amazonaws.com/examples/flower.png',
6
+ prompt:
7
+ 'Detect flowers in this image, draw boxes and output the image, also return total number of flowers',
8
+ },
9
+ {
10
+ title: 'Detecting sharks in video',
11
+ mediaUrl:
12
+ 'https://vision-agent-dev.s3.us-east-2.amazonaws.com/examples/shark3_short.mp4',
13
+ prompt:
14
+ 'Can you detect any surfboards or sharks in the video, draw a green line between the shark and the nearest surfboard and add the distance between them in meters assuming 30 pixels is 1 meter. Make the line red if the shark is within 10 meters of a surfboard. Sample the video at 1 frames per second and save the output video as output.mp4.',
15
+ },
16
+ ];
17
+
18
+ export const PLAN_TITLE = 'Creating instructions';
19
+ export const TOOLS_TITLE = 'Retrieving tools';
20
+ export const GENERATE_CODE_TITLE = 'Generating code';
21
+ export const EXECUTE_CODE_TITLE = 'Executing code';
22
+ export const EXECUTE_CODE_SUCCESS_TITLE = 'Code execution success';
23
+ export const EXECUTE_CODE_FAILURE_TITLE = 'Code execution failure';
package.json CHANGED
@@ -11,7 +11,8 @@
11
  "preview": "next build && next start",
12
  "type-check": "tsc --noEmit",
13
  "format:write": "prettier --write \"{app,lib,components}/**/*.{ts,tsx,mdx}\" --cache",
14
- "format:check": "prettier --check \"{app,lib,components}**/*.{ts,tsx,mdx}\" --cache"
 
15
  },
16
  "dependencies": {
17
  "@aws-sdk/client-s3": "^3.556.0",
@@ -65,6 +66,7 @@
65
  "uuid": "^9.0.1"
66
  },
67
  "devDependencies": {
 
68
  "@tailwindcss/typography": "^0.5.10",
69
  "@types/node": "^20.11.5",
70
  "@types/react": "^18.2.48",
@@ -80,10 +82,10 @@
80
  "eslint-plugin-tailwindcss": "^3.14.0",
81
  "postcss": "^8.4.33",
82
  "prettier": "^3.2.4",
83
- "tailwind-merge": "^2.2.2",
84
- "tailwindcss": "^3.4.1",
85
  "tailwindcss-animate": "^1.0.7",
86
  "typescript": "^5.3.3"
87
  },
88
  "packageManager": "[email protected]"
89
- }
 
11
  "preview": "next build && next start",
12
  "type-check": "tsc --noEmit",
13
  "format:write": "prettier --write \"{app,lib,components}/**/*.{ts,tsx,mdx}\" --cache",
14
+ "format:check": "prettier --check \"{app,lib,components}**/*.{ts,tsx,mdx}\" --cache",
15
+ "test:e2e": "playwright test"
16
  },
17
  "dependencies": {
18
  "@aws-sdk/client-s3": "^3.556.0",
 
66
  "uuid": "^9.0.1"
67
  },
68
  "devDependencies": {
69
+ "@playwright/test": "^1.44.1",
70
  "@tailwindcss/typography": "^0.5.10",
71
  "@types/node": "^20.11.5",
72
  "@types/react": "^18.2.48",
 
82
  "eslint-plugin-tailwindcss": "^3.14.0",
83
  "postcss": "^8.4.33",
84
  "prettier": "^3.2.4",
85
+ "tailwind-merge": "^2.3.0",
86
+ "tailwindcss": "^3.4.4",
87
  "tailwindcss-animate": "^1.0.7",
88
  "typescript": "^5.3.3"
89
  },
90
  "packageManager": "[email protected]"
91
+ }
playwright.config.ts ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig, devices } from '@playwright/test';
2
+
3
+ /**
4
+ * Read environment variables from file.
5
+ * https://github.com/motdotla/dotenv
6
+ */
7
+ // require('dotenv').config();
8
+
9
+ const BASE_URL = process.env.BASE_URL || `http://localhost:3000`;
10
+
11
+ /**
12
+ * See https://playwright.dev/docs/test-configuration.
13
+ */
14
+ export default defineConfig({
15
+ testDir: './tests/e2e',
16
+ /* Run tests in files in parallel */
17
+ fullyParallel: true,
18
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
19
+ forbidOnly: !!process.env.CI,
20
+ /* Retry on CI only */
21
+ retries: process.env.CI ? 2 : 0,
22
+ /* Opt out of parallel tests on CI. */
23
+ workers: process.env.CI ? 1 : undefined,
24
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
25
+ reporter: 'html',
26
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
27
+ use: {
28
+ /* Base URL to use in actions like `await page.goto('/')`. */
29
+ baseURL: BASE_URL,
30
+
31
+ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
32
+ trace: 'on-first-retry',
33
+ },
34
+
35
+ /* Configure projects for major browsers */
36
+ projects: [
37
+ {
38
+ name: 'chromium',
39
+ use: { ...devices['Desktop Chrome'] },
40
+ },
41
+
42
+ /* Test against mobile viewports. */
43
+ // {
44
+ // name: 'Mobile Chrome',
45
+ // use: { ...devices['Pixel 5'] },
46
+ // },
47
+ // {
48
+ // name: 'Mobile Safari',
49
+ // use: { ...devices['iPhone 12'] },
50
+ // },
51
+
52
+ /* Test against branded browsers. */
53
+ // {
54
+ // name: 'Microsoft Edge',
55
+ // use: { ...devices['Desktop Edge'], channel: 'msedge' },
56
+ // },
57
+ {
58
+ name: 'Google Chrome',
59
+ use: { ...devices['Desktop Chrome'], channel: 'chrome' },
60
+ },
61
+ ],
62
+
63
+ /* Run your local dev server before starting the tests */
64
+ webServer: {
65
+ command: 'pnpm dev',
66
+ url: BASE_URL,
67
+ reuseExistingServer: true,
68
+ },
69
+ });
pnpm-lock.yaml CHANGED
@@ -156,9 +156,12 @@ importers:
156
  specifier: ^9.0.1
157
  version: 9.0.1
158
  devDependencies:
 
 
 
159
  '@tailwindcss/typography':
160
  specifier: ^0.5.10
161
- version: 0.5.12([email protected].3)
162
  '@types/node':
163
  specifier: ^20.11.5
164
  version: 20.12.7
@@ -194,7 +197,7 @@ importers:
194
  version: 9.1.0([email protected])
195
  eslint-plugin-tailwindcss:
196
  specifier: ^3.14.0
197
- version: 3.15.1([email protected].3)
198
  postcss:
199
  specifier: ^8.4.33
200
  version: 8.4.38
@@ -202,14 +205,14 @@ importers:
202
  specifier: ^3.2.4
203
  version: 3.2.5
204
  tailwind-merge:
205
- specifier: ^2.2.2
206
- version: 2.2.2
207
  tailwindcss:
208
- specifier: ^3.4.1
209
- version: 3.4.3
210
  tailwindcss-animate:
211
  specifier: ^1.0.7
212
- version: 1.0.7([email protected].3)
213
  typescript:
214
  specifier: ^5.3.3
215
  version: 5.4.5
@@ -442,16 +445,16 @@ packages:
442
  resolution: {integrity: sha512-VXAq/Jz8KIrU84+HqsOJhIKZqG0PNTdi6n6PFQ4xJf44ZQHD/5C7ouH4qCFX5XgZXcgbRIcMVVYGC6Jye0dRng==}
443
  engines: {node: '>=14.0.0'}
444
 
445
- '@babel/[email protected].1':
446
- resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==}
447
  engines: {node: '>=6.9.0'}
448
 
449
- '@babel/helper-validator-identifier@7.22.20':
450
- resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
451
  engines: {node: '>=6.9.0'}
452
 
453
- '@babel/[email protected].4':
454
- resolution: {integrity: sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==}
455
  engines: {node: '>=6.0.0'}
456
  hasBin: true
457
 
@@ -459,8 +462,8 @@ packages:
459
  resolution: {integrity: sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==}
460
  engines: {node: '>=6.9.0'}
461
 
462
- '@babel/[email protected].0':
463
- resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==}
464
  engines: {node: '>=6.9.0'}
465
 
466
  '@emnapi/[email protected]':
@@ -730,6 +733,11 @@ packages:
730
  resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
731
  engines: {node: '>=14'}
732
 
 
 
 
 
 
733
  '@prisma/[email protected]':
734
  resolution: {integrity: sha512-akMSuyvLKeoU4LeyBAUdThP/uhVP3GuLygFE3MlYzaCb3/J8SfsYBE5PkaFuLuVpLyA6sFoW+16z/aPhNAESqg==}
735
  engines: {node: '>=16.13'}
@@ -2209,6 +2217,11 @@ packages:
2209
2210
  resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
2211
 
 
 
 
 
 
2212
2213
  resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
2214
  engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -3108,6 +3121,16 @@ packages:
3108
  resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
3109
  engines: {node: '>= 6'}
3110
 
 
 
 
 
 
 
 
 
 
 
3111
3112
  resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
3113
  engines: {node: '>= 0.4'}
@@ -3442,14 +3465,14 @@ packages:
3442
  engines: {node: '>=10'}
3443
  hasBin: true
3444
 
3445
3446
- resolution: {integrity: sha512-8+pDC1vOedPXjKG7oz8o+iiHrtF2WswaMQJ7CKFpccvSYfrzmvKY9zOJWCg+881722wIHfwkdnRmiiDm9ym+zQ==}
3447
  engines: {node: '>=10'}
3448
  peerDependencies:
3449
  seroval: ^1.0
3450
 
3451
3452
- resolution: {integrity: sha512-TM+Z11tHHvQVQKeNlOUonOWnsNM+2IBwZ4vwoi4j3zKzIpc5IDw8WPwCfcc8F17wy6cBcJGbZbFOR0UCuTZHQA==}
3453
  engines: {node: '>=10'}
3454
 
3455
@@ -3621,16 +3644,16 @@ packages:
3621
3622
  resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
3623
 
3624
- tailwind-merge@2.2.2:
3625
- resolution: {integrity: sha512-tWANXsnmJzgw6mQ07nE3aCDkCK4QdT3ThPMCzawoYA2Pws7vSTCvz3Vrjg61jVUGfFZPJzxEP+NimbcW+EdaDw==}
3626
 
3627
3628
  resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
3629
  peerDependencies:
3630
  tailwindcss: '>=3.0.0 || insiders'
3631
 
3632
3633
- resolution: {integrity: sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==}
3634
  engines: {node: '>=14.0.0'}
3635
  hasBin: true
3636
 
@@ -4560,22 +4583,22 @@ snapshots:
4560
  '@smithy/types': 2.12.0
4561
  tslib: 2.6.2
4562
 
4563
- '@babel/[email protected].1': {}
4564
 
4565
- '@babel/helper-validator-identifier@7.22.20': {}
4566
 
4567
- '@babel/[email protected].4':
4568
  dependencies:
4569
- '@babel/types': 7.24.0
4570
 
4571
  '@babel/[email protected]':
4572
  dependencies:
4573
  regenerator-runtime: 0.14.1
4574
 
4575
- '@babel/[email protected].0':
4576
  dependencies:
4577
- '@babel/helper-string-parser': 7.24.1
4578
- '@babel/helper-validator-identifier': 7.22.20
4579
  to-fast-properties: 2.0.0
4580
 
4581
  '@emnapi/[email protected]':
@@ -4794,6 +4817,10 @@ snapshots:
4794
  '@pkgjs/[email protected]':
4795
  optional: true
4796
 
 
 
 
 
4797
4798
  optionalDependencies:
4799
  prisma: 5.14.0
@@ -5536,13 +5563,13 @@ snapshots:
5536
  dependencies:
5537
  defer-to-connect: 2.0.1
5538
 
5539
- '@tailwindcss/[email protected]([email protected].3)':
5540
  dependencies:
5541
  lodash.castarray: 4.4.0
5542
  lodash.isplainobject: 4.0.6
5543
  lodash.merge: 4.6.2
5544
  postcss-selector-parser: 6.0.10
5545
- tailwindcss: 3.4.3
5546
 
5547
  '@types/[email protected]':
5548
  dependencies:
@@ -5683,7 +5710,7 @@ snapshots:
5683
 
5684
  '@vue/[email protected]':
5685
  dependencies:
5686
- '@babel/parser': 7.24.4
5687
  '@vue/shared': 3.4.23
5688
  entities: 4.5.0
5689
  estree-walker: 2.0.2
@@ -5696,7 +5723,7 @@ snapshots:
5696
 
5697
  '@vue/[email protected]':
5698
  dependencies:
5699
- '@babel/parser': 7.24.4
5700
  '@vue/compiler-core': 3.4.23
5701
  '@vue/compiler-dom': 3.4.23
5702
  '@vue/compiler-ssr': 3.4.23
@@ -6426,11 +6453,11 @@ snapshots:
6426
  semver: 6.3.1
6427
  string.prototype.matchall: 4.0.11
6428
 
6429
6430
  dependencies:
6431
  fast-glob: 3.3.2
6432
  postcss: 8.4.38
6433
- tailwindcss: 3.4.3
6434
 
6435
6436
  dependencies:
@@ -6615,6 +6642,9 @@ snapshots:
6615
 
6616
6617
 
 
 
 
6618
6619
  optional: true
6620
 
@@ -7717,6 +7747,14 @@ snapshots:
7717
 
7718
7719
 
 
 
 
 
 
 
 
 
7720
7721
 
7722
@@ -8084,11 +8122,11 @@ snapshots:
8084
  dependencies:
8085
  lru-cache: 6.0.0
8086
 
8087
8088
  dependencies:
8089
- seroval: 1.0.5
8090
 
8091
8092
 
8093
8094
  dependencies:
@@ -8156,8 +8194,8 @@ snapshots:
8156
8157
  dependencies:
8158
  csstype: 3.1.3
8159
- seroval: 1.0.5
8160
- seroval-plugins: 1.0.5([email protected].5)
8161
 
8162
8163
  dependencies:
@@ -8306,15 +8344,15 @@ snapshots:
8306
 
8307
8308
 
8309
- tailwind-merge@2.2.2:
8310
  dependencies:
8311
  '@babel/runtime': 7.24.4
8312
 
8313
8314
  dependencies:
8315
- tailwindcss: 3.4.3
8316
 
8317
8318
  dependencies:
8319
  '@alloc/quick-lru': 5.2.0
8320
  arg: 5.0.2
 
156
  specifier: ^9.0.1
157
  version: 9.0.1
158
  devDependencies:
159
+ '@playwright/test':
160
+ specifier: ^1.44.1
161
+ version: 1.44.1
162
  '@tailwindcss/typography':
163
  specifier: ^0.5.10
164
+ version: 0.5.12([email protected].4)
165
  '@types/node':
166
  specifier: ^20.11.5
167
  version: 20.12.7
 
197
  version: 9.1.0([email protected])
198
  eslint-plugin-tailwindcss:
199
  specifier: ^3.14.0
200
+ version: 3.15.1([email protected].4)
201
  postcss:
202
  specifier: ^8.4.33
203
  version: 8.4.38
 
205
  specifier: ^3.2.4
206
  version: 3.2.5
207
  tailwind-merge:
208
+ specifier: ^2.3.0
209
+ version: 2.3.0
210
  tailwindcss:
211
+ specifier: ^3.4.4
212
+ version: 3.4.4
213
  tailwindcss-animate:
214
  specifier: ^1.0.7
215
+ version: 1.0.7([email protected].4)
216
  typescript:
217
  specifier: ^5.3.3
218
  version: 5.4.5
 
445
  resolution: {integrity: sha512-VXAq/Jz8KIrU84+HqsOJhIKZqG0PNTdi6n6PFQ4xJf44ZQHD/5C7ouH4qCFX5XgZXcgbRIcMVVYGC6Jye0dRng==}
446
  engines: {node: '>=14.0.0'}
447
 
448
+ '@babel/[email protected].7':
449
+ resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==}
450
  engines: {node: '>=6.9.0'}
451
 
452
+ '@babel/helper-validator-identifier@7.24.7':
453
+ resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==}
454
  engines: {node: '>=6.9.0'}
455
 
456
+ '@babel/[email protected].7':
457
+ resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==}
458
  engines: {node: '>=6.0.0'}
459
  hasBin: true
460
 
 
462
  resolution: {integrity: sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==}
463
  engines: {node: '>=6.9.0'}
464
 
465
+ '@babel/[email protected].7':
466
+ resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==}
467
  engines: {node: '>=6.9.0'}
468
 
469
  '@emnapi/[email protected]':
 
733
  resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
734
  engines: {node: '>=14'}
735
 
736
+ '@playwright/[email protected]':
737
+ resolution: {integrity: sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q==}
738
+ engines: {node: '>=16'}
739
+ hasBin: true
740
+
741
  '@prisma/[email protected]':
742
  resolution: {integrity: sha512-akMSuyvLKeoU4LeyBAUdThP/uhVP3GuLygFE3MlYzaCb3/J8SfsYBE5PkaFuLuVpLyA6sFoW+16z/aPhNAESqg==}
743
  engines: {node: '>=16.13'}
 
2217
2218
  resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
2219
 
2220
2221
+ resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
2222
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
2223
+ os: [darwin]
2224
+
2225
2226
  resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
2227
  engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
 
3121
  resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
3122
  engines: {node: '>= 6'}
3123
 
3124
3125
+ resolution: {integrity: sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==}
3126
+ engines: {node: '>=16'}
3127
+ hasBin: true
3128
+
3129
3130
+ resolution: {integrity: sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg==}
3131
+ engines: {node: '>=16'}
3132
+ hasBin: true
3133
+
3134
3135
  resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
3136
  engines: {node: '>= 0.4'}
 
3465
  engines: {node: '>=10'}
3466
  hasBin: true
3467
 
3468
3469
+ resolution: {integrity: sha512-GO7TkWvodGp6buMEX9p7tNyIkbwlyuAWbI6G9Ec5bhcm7mQdu3JOK1IXbEUwb3FVzSc363GraG/wLW23NSavIw==}
3470
  engines: {node: '>=10'}
3471
  peerDependencies:
3472
  seroval: ^1.0
3473
 
3474
3475
+ resolution: {integrity: sha512-n6ZMQX5q0Vn19Zq7CIKNIo7E75gPkGCFUEqDpa8jgwpYr/vScjqnQ6H09t1uIiZ0ZSK0ypEGvrYK2bhBGWsGdw==}
3476
  engines: {node: '>=10'}
3477
 
3478
 
3644
3645
  resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
3646
 
3647
+ tailwind-merge@2.3.0:
3648
+ resolution: {integrity: sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA==}
3649
 
3650
3651
  resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
3652
  peerDependencies:
3653
  tailwindcss: '>=3.0.0 || insiders'
3654
 
3655
3656
+ resolution: {integrity: sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==}
3657
  engines: {node: '>=14.0.0'}
3658
  hasBin: true
3659
 
 
4583
  '@smithy/types': 2.12.0
4584
  tslib: 2.6.2
4585
 
4586
+ '@babel/[email protected].7': {}
4587
 
4588
+ '@babel/helper-validator-identifier@7.24.7': {}
4589
 
4590
+ '@babel/[email protected].7':
4591
  dependencies:
4592
+ '@babel/types': 7.24.7
4593
 
4594
  '@babel/[email protected]':
4595
  dependencies:
4596
  regenerator-runtime: 0.14.1
4597
 
4598
+ '@babel/[email protected].7':
4599
  dependencies:
4600
+ '@babel/helper-string-parser': 7.24.7
4601
+ '@babel/helper-validator-identifier': 7.24.7
4602
  to-fast-properties: 2.0.0
4603
 
4604
  '@emnapi/[email protected]':
 
4817
  '@pkgjs/[email protected]':
4818
  optional: true
4819
 
4820
+ '@playwright/[email protected]':
4821
+ dependencies:
4822
+ playwright: 1.44.1
4823
+
4824
4825
  optionalDependencies:
4826
  prisma: 5.14.0
 
5563
  dependencies:
5564
  defer-to-connect: 2.0.1
5565
 
5566
+ '@tailwindcss/[email protected]([email protected].4)':
5567
  dependencies:
5568
  lodash.castarray: 4.4.0
5569
  lodash.isplainobject: 4.0.6
5570
  lodash.merge: 4.6.2
5571
  postcss-selector-parser: 6.0.10
5572
+ tailwindcss: 3.4.4
5573
 
5574
  '@types/[email protected]':
5575
  dependencies:
 
5710
 
5711
  '@vue/[email protected]':
5712
  dependencies:
5713
+ '@babel/parser': 7.24.7
5714
  '@vue/shared': 3.4.23
5715
  entities: 4.5.0
5716
  estree-walker: 2.0.2
 
5723
 
5724
  '@vue/[email protected]':
5725
  dependencies:
5726
+ '@babel/parser': 7.24.7
5727
  '@vue/compiler-core': 3.4.23
5728
  '@vue/compiler-dom': 3.4.23
5729
  '@vue/compiler-ssr': 3.4.23
 
6453
  semver: 6.3.1
6454
  string.prototype.matchall: 4.0.11
6455
 
6456
6457
  dependencies:
6458
  fast-glob: 3.3.2
6459
  postcss: 8.4.38
6460
+ tailwindcss: 3.4.4
6461
 
6462
6463
  dependencies:
 
6642
 
6643
6644
 
6645
6646
+ optional: true
6647
+
6648
6649
  optional: true
6650
 
 
7747
 
7748
7749
 
7750
7751
+
7752
7753
+ dependencies:
7754
+ playwright-core: 1.44.1
7755
+ optionalDependencies:
7756
+ fsevents: 2.3.2
7757
+
7758
7759
 
7760
 
8122
  dependencies:
8123
  lru-cache: 6.0.0
8124
 
8125
8126
  dependencies:
8127
+ seroval: 1.0.7
8128
 
8129
8130
 
8131
8132
  dependencies:
 
8194
8195
  dependencies:
8196
  csstype: 3.1.3
8197
+ seroval: 1.0.7
8198
+ seroval-plugins: 1.0.7([email protected].7)
8199
 
8200
8201
  dependencies:
 
8344
 
8345
8346
 
8347
+ tailwind-merge@2.3.0:
8348
  dependencies:
8349
  '@babel/runtime': 7.24.4
8350
 
8351
8352
  dependencies:
8353
+ tailwindcss: 3.4.4
8354
 
8355
8356
  dependencies:
8357
  '@alloc/quick-lru': 5.2.0
8358
  arg: 5.0.2
tests/e2e/index.spec.ts ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ EXAMPLES,
3
+ EXECUTE_CODE_FAILURE_TITLE,
4
+ EXECUTE_CODE_SUCCESS_TITLE,
5
+ PLAN_TITLE,
6
+ TOOLS_TITLE,
7
+ } from '@/lib/constants';
8
+ import { test, expect, type Page } from '@playwright/test';
9
+
10
+ test.beforeEach(async ({ page }) => {
11
+ // https://vercel.com/docs/security/deployment-protection/methods-to-bypass-deployment-protection/protection-bypass-automation
12
+ await page.setExtraHTTPHeaders({
13
+ 'x-vercel-protection-bypass':
14
+ process.env.VERCEL_AUTOMATION_BYPASS_SECRET ?? 'unknown',
15
+ });
16
+ });
17
+
18
+ test('Main page can be opened', async ({ page }) => {
19
+ await page.goto('/');
20
+
21
+ await expect(
22
+ page.getByRole('heading', { name: 'Vision Agent' }),
23
+ ).toBeVisible();
24
+
25
+ await page.getByText(EXAMPLES[0].title).click();
26
+ await expect(page.getByPlaceholder('Message Vision Agent')).toHaveText(
27
+ EXAMPLES[0].prompt,
28
+ );
29
+ });
30
+
31
+ const TEST_EXAMPLE_1_CHAT_ID_1 = 'jN6q4YK';
32
+ const TEST_EXAMPLE_1_CHAT_ID_2 = 'w5ad73s';
33
+
34
+ const checkMainElementVisible = async (page: Page, hasFailingCase = false) => {
35
+ await expect(page.getByText(EXAMPLES[0].prompt)).toBeVisible();
36
+ await expect(
37
+ page.locator(`img[alt*="${EXAMPLES[0].mediaUrl}"]`),
38
+ ).toBeVisible();
39
+
40
+ await expect(page.getByText(PLAN_TITLE)).toBeVisible();
41
+ await expect(page.getByText(TOOLS_TITLE)).toBeVisible();
42
+ if (hasFailingCase) {
43
+ await expect(page.getByText(EXECUTE_CODE_FAILURE_TITLE)).toBeVisible();
44
+ }
45
+ await expect(page.getByText(EXECUTE_CODE_SUCCESS_TITLE)).toBeVisible();
46
+
47
+ await expect(page.locator(`img[alt="result-image-0"]`).first()).toBeVisible();
48
+
49
+ const resultDisplayContainer = await page
50
+ .getByTestId('code-result-display-container')
51
+ .locator('visible=true');
52
+ await expect(resultDisplayContainer).toBeVisible();
53
+
54
+ await resultDisplayContainer
55
+ .getByTestId('open-final-test-code-dialog-button')
56
+ .click();
57
+ const dialog = await page.getByRole('dialog');
58
+ await expect(
59
+ dialog.getByRole('heading', { name: 'Test code' }),
60
+ ).toBeVisible();
61
+ await dialog
62
+ .getByRole('button')
63
+ .filter({
64
+ hasText: 'Close',
65
+ })
66
+ .click();
67
+ await resultDisplayContainer
68
+ .getByTestId('view-all-result-images-button')
69
+ .click();
70
+ await expect(page.locator(`img[alt="detail-result-image-0"]`)).toBeVisible();
71
+ };
72
+
73
+ test.describe('Page with example 1 can be opened', () => {
74
+ test('Page with old data structiure can be opened', async ({ page }) => {
75
+ await page.goto(`/chat/${TEST_EXAMPLE_1_CHAT_ID_1}`);
76
+
77
+ await checkMainElementVisible(page, true);
78
+ });
79
+
80
+ test('Page with new data structiure can be opened', async ({ page }) => {
81
+ await page.goto(`/chat/${TEST_EXAMPLE_1_CHAT_ID_2}`);
82
+
83
+ await checkMainElementVisible(page);
84
+ });
85
+ });