Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
B
bit-pm
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
燕伟桐
bit-pm
Commits
c0adf8d3
Unverified
提交
c0adf8d3
authored
6月 01, 2025
作者:
Will Chen
提交者:
GitHub
6月 01, 2025
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Attach image e2e tests (#301)
上级
44048235
显示空白字符变更
内嵌
并排
正在显示
10 个修改的文件
包含
133 行增加
和
18 行删除
+133
-18
attach_image.spec.ts
e2e-tests/attach_image.spec.ts
+30
-0
logo.png
e2e-tests/fixtures/images/logo.png
+0
-0
test_helper.ts
e2e-tests/helpers/test_helper.ts
+49
-12
attach_image.spec.ts_attach-image---chat-1.aria.yml
...shots/attach_image.spec.ts_attach-image---chat-1.aria.yml
+26
-0
attach_image.spec.ts_attach-image---home-chat-1.aria.yml
.../attach_image.spec.ts_attach-image---home-chat-1.aria.yml
+8
-0
attach_image.spec.ts_server-dump.txt
e2e-tests/snapshots/attach_image.spec.ts_server-dump.txt
+4
-0
playwright.config.ts
playwright.config.ts
+1
-1
ChatInput.tsx
src/components/chat/ChatInput.tsx
+1
-1
HomeChatInput.tsx
src/components/chat/HomeChatInput.tsx
+1
-1
index.ts
testing/fake-llm-server/index.ts
+13
-3
没有找到文件。
e2e-tests/attach_image.spec.ts
0 → 100644
浏览文件 @
c0adf8d3
import
{
test
}
from
"./helpers/test_helper"
;
// attach image is implemented in two separate components
// - HomeChatInput
// - ChatInput
// so we need to test both
test
(
"attach image - home chat"
,
async
({
po
})
=>
{
await
po
.
setUp
();
await
po
.
getHomeChatInputContainer
()
.
locator
(
"input[type='file']"
)
.
setInputFiles
(
"e2e-tests/fixtures/images/logo.png"
);
await
po
.
sendPrompt
(
"[dump]"
);
await
po
.
snapshotServerDump
({
onlyLastMessage
:
true
});
await
po
.
snapshotMessages
({
replaceDumpPath
:
true
});
});
test
(
"attach image - chat"
,
async
({
po
})
=>
{
await
po
.
setUp
({
autoApprove
:
true
});
await
po
.
sendPrompt
(
"basic"
);
await
po
.
getChatInputContainer
()
.
locator
(
"input[type='file']"
)
.
setInputFiles
(
"e2e-tests/fixtures/images/logo.png"
);
await
po
.
sendPrompt
(
"[dump]"
);
await
po
.
snapshotServerDump
({
onlyLastMessage
:
true
});
await
po
.
snapshotMessages
({
replaceDumpPath
:
true
});
});
e2e-tests/fixtures/images/logo.png
0 → 100644
浏览文件 @
c0adf8d3
1.2 KB
e2e-tests/helpers/test_helper.ts
浏览文件 @
c0adf8d3
...
@@ -20,7 +20,25 @@ class PageObject {
...
@@ -20,7 +20,25 @@ class PageObject {
await
this
.
selectTestModel
();
await
this
.
selectTestModel
();
}
}
async
snapshotMessages
()
{
async
snapshotMessages
({
replaceDumpPath
=
false
,
}:
{
replaceDumpPath
?:
boolean
}
=
{})
{
if
(
replaceDumpPath
)
{
// Update page so that "[[dyad-dump-path=*]]" is replaced with a placeholder path
// which is stable across runs.
await
this
.
page
.
evaluate
(()
=>
{
const
messagesList
=
document
.
querySelector
(
"[data-testid=messages-list]"
,
);
if
(
!
messagesList
)
{
throw
new
Error
(
"Messages list not found"
);
}
messagesList
.
innerHTML
=
messagesList
.
innerHTML
.
replace
(
/
\[\[
dyad-dump-path=
([^\]]
+
)\]\]
/
,
"[[dyad-dump-path=*]]"
,
);
});
}
await
expect
(
this
.
page
.
getByTestId
(
"messages-list"
)).
toMatchAriaSnapshot
();
await
expect
(
this
.
page
.
getByTestId
(
"messages-list"
)).
toMatchAriaSnapshot
();
}
}
...
@@ -41,7 +59,9 @@ class PageObject {
...
@@ -41,7 +59,9 @@ class PageObject {
await
expect
(
iframe
.
contentFrame
().
locator
(
"body"
)).
toMatchAriaSnapshot
();
await
expect
(
iframe
.
contentFrame
().
locator
(
"body"
)).
toMatchAriaSnapshot
();
}
}
async
snapshotServerDump
()
{
async
snapshotServerDump
({
onlyLastMessage
=
false
,
}:
{
onlyLastMessage
?:
boolean
}
=
{})
{
// Get the text content of the messages list
// Get the text content of the messages list
const
messagesListText
=
await
this
.
page
const
messagesListText
=
await
this
.
page
.
getByTestId
(
"messages-list"
)
.
getByTestId
(
"messages-list"
)
...
@@ -62,7 +82,9 @@ class PageObject {
...
@@ -62,7 +82,9 @@ class PageObject {
const
dumpContent
=
fs
.
readFileSync
(
dumpFilePath
,
"utf-8"
);
const
dumpContent
=
fs
.
readFileSync
(
dumpFilePath
,
"utf-8"
);
// Perform snapshot comparison
// Perform snapshot comparison
expect
(
prettifyDump
(
dumpContent
)).
toMatchSnapshot
(
"server-dump.txt"
);
expect
(
prettifyDump
(
dumpContent
,
{
onlyLastMessage
})).
toMatchSnapshot
(
"server-dump.txt"
,
);
}
}
async
waitForChatCompletion
()
{
async
waitForChatCompletion
()
{
...
@@ -85,13 +107,21 @@ class PageObject {
...
@@ -85,13 +107,21 @@ class PageObject {
return
this
.
page
.
getByRole
(
"button"
,
{
name
:
"Undo"
});
return
this
.
page
.
getByRole
(
"button"
,
{
name
:
"Undo"
});
}
}
getHomeChatInputContainer
()
{
return
this
.
page
.
getByTestId
(
"home-chat-input-container"
);
}
getChatInputContainer
()
{
return
this
.
page
.
getByTestId
(
"chat-input-container"
);
}
getChatInput
()
{
return
this
.
page
.
getByRole
(
"textbox"
,
{
name
:
"Ask Dyad to build..."
});
}
async
sendPrompt
(
prompt
:
string
)
{
async
sendPrompt
(
prompt
:
string
)
{
await
this
.
page
await
this
.
getChatInput
().
click
();
.
getByRole
(
"textbox"
,
{
name
:
"Ask Dyad to build..."
})
await
this
.
getChatInput
().
fill
(
prompt
);
.
click
();
await
this
.
page
.
getByRole
(
"textbox"
,
{
name
:
"Ask Dyad to build..."
})
.
fill
(
prompt
);
await
this
.
page
.
getByRole
(
"button"
,
{
name
:
"Send message"
}).
click
();
await
this
.
page
.
getByRole
(
"button"
,
{
name
:
"Send message"
}).
click
();
await
this
.
waitForChatCompletion
();
await
this
.
waitForChatCompletion
();
}
}
...
@@ -310,15 +340,22 @@ export const test = base.extend<{
...
@@ -310,15 +340,22 @@ export const test = base.extend<{
],
],
});
});
function
prettifyDump
(
dumpContent
:
string
)
{
function
prettifyDump
(
dumpContent
:
string
,
{
onlyLastMessage
=
false
}:
{
onlyLastMessage
?:
boolean
}
=
{},
)
{
const
parsedDump
=
JSON
.
parse
(
dumpContent
)
as
Array
<
{
const
parsedDump
=
JSON
.
parse
(
dumpContent
)
as
Array
<
{
role
:
string
;
role
:
string
;
content
:
string
;
content
:
string
;
}
>
;
}
>
;
return
parsedDump
const
messages
=
onlyLastMessage
?
parsedDump
.
slice
(
-
1
)
:
parsedDump
;
return
messages
.
map
((
message
)
=>
{
.
map
((
message
)
=>
{
const
content
=
message
.
content
const
content
=
Array
.
isArray
(
message
.
content
)
?
JSON
.
stringify
(
message
.
content
)
:
message
.
content
// We remove package.json because it's flaky.
// We remove package.json because it's flaky.
// Depending on whether pnpm install is run, it will be modified,
// Depending on whether pnpm install is run, it will be modified,
// and the contents and timestamp (thus affecting order) will be affected.
// and the contents and timestamp (thus affecting order) will be affected.
...
...
e2e-tests/snapshots/attach_image.spec.ts_attach-image---chat-1.aria.yml
0 → 100644
浏览文件 @
c0adf8d3
-
paragraph
:
basic
-
'
button
"Thinking
`<dyad-write>`:
I'
'
ll
think
about
the
problem
and
write
a
bug
report.
<dyad-write>
<dyad-write
path=\"file1.txt\">
Fake
dyad
write
</dyad-write>"'
:
-
img
-
img
-
paragraph
:
-
code
:
"
`<dyad-write>`"
-
text
:
"
:
I'll
think
about
the
problem
and
write
a
bug
report."
-
paragraph
:
<dyad-write>
-
paragraph
:
<dyad-write path="file1.txt"> Fake dyad write </dyad-write>
-
img
-
text
:
file1.txt
-
img
-
text
:
file1.txt
-
paragraph
:
More EOM
-
img
-
text
:
Approved
-
paragraph
:
"
[dump]"
-
paragraph
:
"
Attachments:"
-
list
:
-
listitem
:
logo.png (image/png)
-
paragraph
:
"
[[dyad-dump-path=*]]"
-
img
-
text
:
Approved
-
button "Retry"
:
-
img
\ No newline at end of file
e2e-tests/snapshots/attach_image.spec.ts_attach-image---home-chat-1.aria.yml
0 → 100644
浏览文件 @
c0adf8d3
-
paragraph
:
"
[dump]"
-
paragraph
:
"
Attachments:"
-
list
:
-
listitem
:
logo.png (image/png)
-
paragraph
:
"
[[dyad-dump-path=*]]"
-
button "Retry"
:
-
img
\ No newline at end of file
e2e-tests/snapshots/attach_image.spec.ts_server-dump.txt
0 → 100644
浏览文件 @
c0adf8d3
===
role: user
message: [{"type":"text","text":"[dump]\n\nAttachments:\n- logo.png (image/png)"},{"type":"image_url","image_url":{"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAhGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAAAAAAAEgAAAABAAAASAAAAAEAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAEKADAAQAAAABAAAAEAAAAADHbxzxAAAACXBIWXMAAAsTAAALEwEAmpwYAAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgoZXuEHAAACbElEQVQ4EY1TTWgTQRR+M7NpQoKmPVTPQi/qoaDWQAsxIigqRS/JzYNHxVz0oAcrG6hQoV68ePNQECUpeBFBIqQiVBrbaqvgoXjRSzVJf8zfzu7MG2eWbGywFN8e3ux73/fNm/dmAP7TFCgSQHeurSC4t1eEAFET4+WjzPIq5AX5ZURMjO5GNEk7VbJKKWU9Or8WBg2cvPRxlLXiX7arVtFWNjVkzSX/VJBPK0YKRMIciI6477kjhsDrAxfJoebVUwd0bl1vBD0ChpzR5JkrKzGvjum2B8MtrphLRXGTY5RKCaioi7wr/lcgID+//Omsu4EzERg4GIIoWAygxrezwOvNhmiAoCrkCtZtqF9BPp33d86nl46jw173aWKtXWtzWi21kELDgzOo+mJCCRBIFIowBr3rOQK4bDJC90PVrf5Ayi/efDP62QBvn14Y5m0sKogOCoWy/nvL55syqOl4ppCRT6+9GxAKjgmtLYg3ldVkM4Hs0Fr4QSmxwi28T1gUHE+J9rpGdozqRvoWcmKWUMpyEXWH2IYJiq0KtQYr/qgRWc3T4lJ/IbNVx/xmmCrMXJ+ML3+wZP+Jmre5MN8/NVYoFKTB2XbJ+vYyPi9l/4hLq+XZpZMJ0BxzP3z1XGpO99qUDg897VFGEkd+3lm9lVy8e2NsceL7q/iqwvCIohIYU9MGm+pwuuOwbUVtm+D0uXIO5b57noxCWzJoSQJNIaAhm8BxMze7PGbboLFA/El0BYxqcJTJC+Vkg5PrLU4PO1KBg/jVo87jZ++Tb4PSDX5XMxdq14QOpvfI9XDMxTKPKQim9DqtY8H/Tv8HGFE+AZtzYdAAAAAASUVORK5CYII="}}]
\ No newline at end of file
playwright.config.ts
浏览文件 @
c0adf8d3
...
@@ -28,7 +28,7 @@ const config: PlaywrightTestConfig = {
...
@@ -28,7 +28,7 @@ const config: PlaywrightTestConfig = {
},
},
webServer
:
{
webServer
:
{
command
:
`cd testing/fake-llm-server && npm start`
,
command
:
`cd testing/fake-llm-server && npm
run build && npm
start`
,
url
:
"http://localhost:3500/health"
,
url
:
"http://localhost:3500/health"
,
},
},
};
};
...
...
src/components/chat/ChatInput.tsx
浏览文件 @
c0adf8d3
...
@@ -253,7 +253,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
...
@@ -253,7 +253,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
Error loading proposal:
{
proposalError
}
Error loading proposal:
{
proposalError
}
</
div
>
</
div
>
)
}
)
}
<
div
className=
"p-4"
>
<
div
className=
"p-4"
data
-
testid=
"chat-input-container"
>
<
div
<
div
className=
{
`relative flex flex-col border border-border rounded-lg bg-(--background-lighter) shadow-sm ${
className=
{
`relative flex flex-col border border-border rounded-lg bg-(--background-lighter) shadow-sm ${
isDraggingOver ? "ring-2 ring-blue-500 border-blue-500" : ""
isDraggingOver ? "ring-2 ring-blue-500 border-blue-500" : ""
...
...
src/components/chat/HomeChatInput.tsx
浏览文件 @
c0adf8d3
...
@@ -79,7 +79,7 @@ export function HomeChatInput({
...
@@ -79,7 +79,7 @@ export function HomeChatInput({
return
(
return
(
<>
<>
<
div
className=
"p-4"
>
<
div
className=
"p-4"
data
-
testid=
"home-chat-input-container"
>
<
div
<
div
className=
{
`relative flex flex-col space-y-2 border border-border rounded-lg bg-(--background-lighter) shadow-sm ${
className=
{
`relative flex flex-col space-y-2 border border-border rounded-lg bg-(--background-lighter) shadow-sm ${
isDraggingOver ? "ring-2 ring-blue-500 border-blue-500" : ""
isDraggingOver ? "ring-2 ring-blue-500 border-blue-500" : ""
...
...
testing/fake-llm-server/index.ts
浏览文件 @
c0adf8d3
...
@@ -7,7 +7,8 @@ import path from "path";
...
@@ -7,7 +7,8 @@ import path from "path";
// Create Express app
// Create Express app
const
app
=
express
();
const
app
=
express
();
app
.
use
(
cors
());
app
.
use
(
cors
());
app
.
use
(
express
.
json
());
app
.
use
(
express
.
json
({
limit
:
"50mb"
}));
app
.
use
(
express
.
urlencoded
({
extended
:
true
,
limit
:
"50mb"
}));
const
PORT
=
3500
;
const
PORT
=
3500
;
...
@@ -205,9 +206,17 @@ function chatCompletionHandler(req: Request, res: Response) {
...
@@ -205,9 +206,17 @@ function chatCompletionHandler(req: Request, res: Response) {
}
}
let
messageContent
=
CANNED_MESSAGE
;
let
messageContent
=
CANNED_MESSAGE
;
console
.
error
(
"LASTMESSAGE"
,
lastMessage
);
// Check if the last message is "[dump]" to write messages to file and return path
// Check if the last message is "[dump]" to write messages to file and return path
if
(
lastMessage
&&
lastMessage
.
content
===
"[dump]"
)
{
if
(
lastMessage
&&
(
Array
.
isArray
(
lastMessage
.
content
)
?
lastMessage
.
content
.
some
(
(
part
:
{
type
:
string
;
text
:
string
})
=>
part
.
type
===
"text"
&&
part
.
text
.
includes
(
"[dump]"
),
)
:
lastMessage
.
content
.
includes
(
"[dump]"
))
)
{
const
timestamp
=
Date
.
now
();
const
timestamp
=
Date
.
now
();
const
generatedDir
=
path
.
join
(
__dirname
,
"generated"
);
const
generatedDir
=
path
.
join
(
__dirname
,
"generated"
);
...
@@ -241,6 +250,7 @@ function chatCompletionHandler(req: Request, res: Response) {
...
@@ -241,6 +250,7 @@ function chatCompletionHandler(req: Request, res: Response) {
if
(
if
(
lastMessage
&&
lastMessage
&&
lastMessage
.
content
&&
lastMessage
.
content
&&
typeof
lastMessage
.
content
===
"string"
&&
lastMessage
.
content
.
startsWith
(
"tc="
)
lastMessage
.
content
.
startsWith
(
"tc="
)
)
{
)
{
const
testCaseName
=
lastMessage
.
content
.
slice
(
3
);
// Remove "tc=" prefix
const
testCaseName
=
lastMessage
.
content
.
slice
(
3
);
// Remove "tc=" prefix
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论