test(core): split and enhance copilot e2e tests (#11007)
### TL;DR
Split and enhance copilot e2e tests.
### What Changed
#### Tests Structure
The e2e tests are organized into the following categories:
1. **Basic Tests (`/basic`)**: Tests for verifying core AI capabilities including feature onboarding, authorization workflows, and basic chat interactions.
2. **Chat Interaction Tests (`/chat-with`)**: Tests for verifying the AI's interaction with various object types, such as attachments, images, text content, Edgeless elements, etc.
3. **AI Action Tests (`/ai-action`)**: Tests for verifying the AI's actions, such as text translation, gramma correction, etc.
4. **Insertion Tests (`/insertion`)**: Tests for verifying answer insertion functionality.
#### Tests Writing
Writing a copilot test cases is easier and clear
e.g.
```ts
test('support chat with specified doc', async ({ page, utils }) => {
// Initialize the doc
await focusDocTitle(page);
await page.keyboard.insertText('Test Doc');
await page.keyboard.press('Enter');
await page.keyboard.insertText('EEee is a cute cat');
await utils.chatPanel.chatWithDoc(page, 'Test Doc');
await utils.chatPanel.makeChat(page, 'What is EEee?');
await utils.chatPanel.waitForHistory(page, [
{
role: 'user',
content: 'What is EEee?',
},
{
role: 'assistant',
status: 'success',
},
]);
const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
expect(content).toMatch(/EEee/);
});
```
#### Summary
||Cases|
|------|----|
|Before|19||
|After|151||
> Close BS-2769
This commit is contained in:
@@ -49,6 +49,7 @@ import {
|
|||||||
export const translateSubItem: AISubItemConfig[] = translateLangs.map(lang => {
|
export const translateSubItem: AISubItemConfig[] = translateLangs.map(lang => {
|
||||||
return {
|
return {
|
||||||
type: lang,
|
type: lang,
|
||||||
|
testId: `action-translate-${lang}`,
|
||||||
handler: actionToHandler('translate', AIStarIconWithAnimation, { lang }),
|
handler: actionToHandler('translate', AIStarIconWithAnimation, { lang }),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -56,6 +57,7 @@ export const translateSubItem: AISubItemConfig[] = translateLangs.map(lang => {
|
|||||||
export const toneSubItem: AISubItemConfig[] = textTones.map(tone => {
|
export const toneSubItem: AISubItemConfig[] = textTones.map(tone => {
|
||||||
return {
|
return {
|
||||||
type: tone,
|
type: tone,
|
||||||
|
testId: `action-change-tone-${tone.toLowerCase()}`,
|
||||||
handler: actionToHandler('changeTone', AIStarIconWithAnimation, { tone }),
|
handler: actionToHandler('changeTone', AIStarIconWithAnimation, { tone }),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -66,6 +68,7 @@ export function createImageFilterSubItem(
|
|||||||
return imageFilterStyles.map(style => {
|
return imageFilterStyles.map(style => {
|
||||||
return {
|
return {
|
||||||
type: style,
|
type: style,
|
||||||
|
testId: `action-image-filter-${style.toLowerCase().replace(' ', '-')}`,
|
||||||
handler: actionToHandler(
|
handler: actionToHandler(
|
||||||
'filterImage',
|
'filterImage',
|
||||||
AIImageIconWithAnimation,
|
AIImageIconWithAnimation,
|
||||||
@@ -84,6 +87,7 @@ export function createImageProcessingSubItem(
|
|||||||
return imageProcessingTypes.map(type => {
|
return imageProcessingTypes.map(type => {
|
||||||
return {
|
return {
|
||||||
type,
|
type,
|
||||||
|
testId: `action-image-processing-${type.toLowerCase().replace(' ', '-')}`,
|
||||||
handler: actionToHandler(
|
handler: actionToHandler(
|
||||||
'processImage',
|
'processImage',
|
||||||
AIImageIconWithAnimation,
|
AIImageIconWithAnimation,
|
||||||
@@ -146,36 +150,42 @@ const EditAIGroup: AIItemGroupConfig = {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'Translate to',
|
name: 'Translate to',
|
||||||
|
testId: 'action-translate',
|
||||||
icon: LanguageIcon(),
|
icon: LanguageIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
subItem: translateSubItem,
|
subItem: translateSubItem,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Change tone to',
|
name: 'Change tone to',
|
||||||
|
testId: 'action-change-tone',
|
||||||
icon: ToneIcon(),
|
icon: ToneIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
subItem: toneSubItem,
|
subItem: toneSubItem,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Improve writing',
|
name: 'Improve writing',
|
||||||
|
testId: 'action-improve-writing',
|
||||||
icon: ImproveWritingIcon(),
|
icon: ImproveWritingIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
handler: actionToHandler('improveWriting', AIStarIconWithAnimation),
|
handler: actionToHandler('improveWriting', AIStarIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Make it longer',
|
name: 'Make it longer',
|
||||||
|
testId: 'action-make-it-longer',
|
||||||
icon: LongerIcon(),
|
icon: LongerIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
handler: actionToHandler('makeLonger', AIStarIconWithAnimation),
|
handler: actionToHandler('makeLonger', AIStarIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Make it shorter',
|
name: 'Make it shorter',
|
||||||
|
testId: 'action-make-it-shorter',
|
||||||
icon: ShorterIcon(),
|
icon: ShorterIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
handler: actionToHandler('makeShorter', AIStarIconWithAnimation),
|
handler: actionToHandler('makeShorter', AIStarIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Continue writing',
|
name: 'Continue writing',
|
||||||
|
testId: 'action-continue-writing',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
handler: actionToHandler('continueWriting', AIPenIconWithAnimation),
|
handler: actionToHandler('continueWriting', AIPenIconWithAnimation),
|
||||||
@@ -188,30 +198,35 @@ const DraftAIGroup: AIItemGroupConfig = {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'Write an article about this',
|
name: 'Write an article about this',
|
||||||
|
testId: 'action-write-article',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
handler: actionToHandler('writeArticle', AIPenIconWithAnimation),
|
handler: actionToHandler('writeArticle', AIPenIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Write a tweet about this',
|
name: 'Write a tweet about this',
|
||||||
|
testId: 'action-write-twitter-post',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
handler: actionToHandler('writeTwitterPost', AIPenIconWithAnimation),
|
handler: actionToHandler('writeTwitterPost', AIPenIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Write a poem about this',
|
name: 'Write a poem about this',
|
||||||
|
testId: 'action-write-poem',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
handler: actionToHandler('writePoem', AIPenIconWithAnimation),
|
handler: actionToHandler('writePoem', AIPenIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Write a blog post about this',
|
name: 'Write a blog post about this',
|
||||||
|
testId: 'action-write-blog-post',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
handler: actionToHandler('writeBlogPost', AIPenIconWithAnimation),
|
handler: actionToHandler('writeBlogPost', AIPenIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Brainstorm ideas about this',
|
name: 'Brainstorm ideas about this',
|
||||||
|
testId: 'action-brainstorm',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
handler: actionToHandler('brainstorm', AIPenIconWithAnimation),
|
handler: actionToHandler('brainstorm', AIPenIconWithAnimation),
|
||||||
@@ -224,36 +239,42 @@ const ReviewWIthAIGroup: AIItemGroupConfig = {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'Fix spelling',
|
name: 'Fix spelling',
|
||||||
|
testId: 'action-fix-spelling',
|
||||||
icon: DoneIcon(),
|
icon: DoneIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
handler: actionToHandler('fixSpelling', AIStarIconWithAnimation),
|
handler: actionToHandler('fixSpelling', AIStarIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Fix grammar',
|
name: 'Fix grammar',
|
||||||
|
testId: 'action-fix-grammar',
|
||||||
icon: DoneIcon(),
|
icon: DoneIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
handler: actionToHandler('improveGrammar', AIStarIconWithAnimation),
|
handler: actionToHandler('improveGrammar', AIStarIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Explain this image',
|
name: 'Explain this image',
|
||||||
|
testId: 'action-explain-image',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
showWhen: imageBlockShowWhen,
|
showWhen: imageBlockShowWhen,
|
||||||
handler: actionToHandler('explainImage', AIStarIconWithAnimation),
|
handler: actionToHandler('explainImage', AIStarIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Explain this code',
|
name: 'Explain this code',
|
||||||
|
testId: 'action-explain-code',
|
||||||
icon: ExplainIcon(),
|
icon: ExplainIcon(),
|
||||||
showWhen: codeBlockShowWhen,
|
showWhen: codeBlockShowWhen,
|
||||||
handler: actionToHandler('explainCode', AIStarIconWithAnimation),
|
handler: actionToHandler('explainCode', AIStarIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Check code error',
|
name: 'Check code error',
|
||||||
|
testId: 'action-check-code-error',
|
||||||
icon: ExplainIcon(),
|
icon: ExplainIcon(),
|
||||||
showWhen: codeBlockShowWhen,
|
showWhen: codeBlockShowWhen,
|
||||||
handler: actionToHandler('checkCodeErrors', AIStarIconWithAnimation),
|
handler: actionToHandler('checkCodeErrors', AIStarIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Explain selection',
|
name: 'Explain selection',
|
||||||
|
testId: 'action-explain-selection',
|
||||||
icon: SelectionIcon(),
|
icon: SelectionIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
handler: actionToHandler('explain', AIStarIconWithAnimation),
|
handler: actionToHandler('explain', AIStarIconWithAnimation),
|
||||||
@@ -266,12 +287,14 @@ const GenerateWithAIGroup: AIItemGroupConfig = {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'Summarize',
|
name: 'Summarize',
|
||||||
|
testId: 'action-summarize',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
handler: actionToHandler('summary', AIPenIconWithAnimation),
|
handler: actionToHandler('summary', AIPenIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Generate headings',
|
name: 'Generate headings',
|
||||||
|
testId: 'action-generate-headings',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
beta: true,
|
beta: true,
|
||||||
handler: actionToHandler('createHeadings', AIPenIconWithAnimation),
|
handler: actionToHandler('createHeadings', AIPenIconWithAnimation),
|
||||||
@@ -293,24 +316,28 @@ const GenerateWithAIGroup: AIItemGroupConfig = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Generate an image',
|
name: 'Generate an image',
|
||||||
|
testId: 'action-generate-image',
|
||||||
icon: ImageIcon(),
|
icon: ImageIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
handler: actionToHandler('createImage', AIImageIconWithAnimation),
|
handler: actionToHandler('createImage', AIImageIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Generate outline',
|
name: 'Generate outline',
|
||||||
|
testId: 'action-generate-outline',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
handler: actionToHandler('writeOutline', AIPenIconWithAnimation),
|
handler: actionToHandler('writeOutline', AIPenIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Brainstorm ideas with mind map',
|
name: 'Brainstorm ideas with mind map',
|
||||||
|
testId: 'action-brainstorm-mindmap',
|
||||||
icon: MindmapIcon(),
|
icon: MindmapIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
handler: actionToHandler('brainstormMindmap', AIPenIconWithAnimation),
|
handler: actionToHandler('brainstormMindmap', AIPenIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Generate presentation',
|
name: 'Generate presentation',
|
||||||
|
testId: 'action-generate-presentation',
|
||||||
icon: PresentationIcon(),
|
icon: PresentationIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
handler: actionToHandler('createSlides', AIPresentationIconWithAnimation),
|
handler: actionToHandler('createSlides', AIPresentationIconWithAnimation),
|
||||||
@@ -318,6 +345,7 @@ const GenerateWithAIGroup: AIItemGroupConfig = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Make it real',
|
name: 'Make it real',
|
||||||
|
testId: 'action-make-it-real',
|
||||||
icon: MakeItRealIcon(),
|
icon: MakeItRealIcon(),
|
||||||
beta: true,
|
beta: true,
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
@@ -325,6 +353,7 @@ const GenerateWithAIGroup: AIItemGroupConfig = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Find actions',
|
name: 'Find actions',
|
||||||
|
testId: 'action-find-actions',
|
||||||
icon: SearchIcon(),
|
icon: SearchIcon(),
|
||||||
showWhen: textBlockShowWhen,
|
showWhen: textBlockShowWhen,
|
||||||
handler: actionToHandler('findActions', AIStarIconWithAnimation),
|
handler: actionToHandler('findActions', AIStarIconWithAnimation),
|
||||||
@@ -338,6 +367,7 @@ const OthersAIGroup: AIItemGroupConfig = {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'Continue with AI',
|
name: 'Continue with AI',
|
||||||
|
testId: 'action-continue-with-ai',
|
||||||
icon: CommentIcon(),
|
icon: CommentIcon(),
|
||||||
handler: host => {
|
handler: host => {
|
||||||
const panel = getAIPanelWidget(host);
|
const panel = getAIPanelWidget(host);
|
||||||
@@ -366,6 +396,7 @@ export function buildAIImageItemGroups(): AIItemGroupConfig[] {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'Explain this image',
|
name: 'Explain this image',
|
||||||
|
testId: 'action-explain-image',
|
||||||
icon: ImageIcon(),
|
icon: ImageIcon(),
|
||||||
showWhen: () => true,
|
showWhen: () => true,
|
||||||
handler: actionToHandler(
|
handler: actionToHandler(
|
||||||
@@ -382,6 +413,7 @@ export function buildAIImageItemGroups(): AIItemGroupConfig[] {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'Generate an image',
|
name: 'Generate an image',
|
||||||
|
testId: 'action-generate-image',
|
||||||
icon: ImageIcon(),
|
icon: ImageIcon(),
|
||||||
showWhen: () => true,
|
showWhen: () => true,
|
||||||
handler: actionToHandler(
|
handler: actionToHandler(
|
||||||
@@ -393,6 +425,7 @@ export function buildAIImageItemGroups(): AIItemGroupConfig[] {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Image processing',
|
name: 'Image processing',
|
||||||
|
testId: 'action-image-processing',
|
||||||
icon: ImageIcon(),
|
icon: ImageIcon(),
|
||||||
showWhen: () => true,
|
showWhen: () => true,
|
||||||
subItem: createImageProcessingSubItem(blockActionTrackerOptions),
|
subItem: createImageProcessingSubItem(blockActionTrackerOptions),
|
||||||
@@ -401,6 +434,7 @@ export function buildAIImageItemGroups(): AIItemGroupConfig[] {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'AI image filter',
|
name: 'AI image filter',
|
||||||
|
testId: 'action-ai-image-filter',
|
||||||
icon: ImproveWritingIcon(),
|
icon: ImproveWritingIcon(),
|
||||||
showWhen: () => true,
|
showWhen: () => true,
|
||||||
subItem: createImageFilterSubItem(blockActionTrackerOptions),
|
subItem: createImageFilterSubItem(blockActionTrackerOptions),
|
||||||
@@ -409,6 +443,7 @@ export function buildAIImageItemGroups(): AIItemGroupConfig[] {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Generate a caption',
|
name: 'Generate a caption',
|
||||||
|
testId: 'action-generate-caption',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
showWhen: () => true,
|
showWhen: () => true,
|
||||||
beta: true,
|
beta: true,
|
||||||
@@ -432,6 +467,7 @@ export function buildAICodeItemGroups(): AIItemGroupConfig[] {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'Explain this code',
|
name: 'Explain this code',
|
||||||
|
testId: 'action-explain-code',
|
||||||
icon: ExplainIcon(),
|
icon: ExplainIcon(),
|
||||||
showWhen: () => true,
|
showWhen: () => true,
|
||||||
handler: actionToHandler(
|
handler: actionToHandler(
|
||||||
@@ -443,6 +479,7 @@ export function buildAICodeItemGroups(): AIItemGroupConfig[] {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Check code error',
|
name: 'Check code error',
|
||||||
|
testId: 'action-check-code-error',
|
||||||
icon: ExplainIcon(),
|
icon: ExplainIcon(),
|
||||||
showWhen: () => true,
|
showWhen: () => true,
|
||||||
handler: actionToHandler(
|
handler: actionToHandler(
|
||||||
|
|||||||
@@ -462,7 +462,6 @@ export function noteBlockOrTextShowWhen(
|
|||||||
host: EditorHost
|
host: EditorHost
|
||||||
) {
|
) {
|
||||||
const selected = getCopilotSelectedElems(host);
|
const selected = getCopilotSelectedElems(host);
|
||||||
|
|
||||||
return selected.some(
|
return selected.some(
|
||||||
el =>
|
el =>
|
||||||
el instanceof NoteBlockModel ||
|
el instanceof NoteBlockModel ||
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ export function discard(
|
|||||||
return {
|
return {
|
||||||
name: 'Discard',
|
name: 'Discard',
|
||||||
icon: DeleteIcon(),
|
icon: DeleteIcon(),
|
||||||
|
testId: 'answer-discard',
|
||||||
showWhen: () => !!panel.answer,
|
showWhen: () => !!panel.answer,
|
||||||
handler: () => {
|
handler: () => {
|
||||||
panel.discard();
|
panel.discard();
|
||||||
@@ -96,6 +97,7 @@ export function retry(panel: AffineAIPanelWidget): AIItemConfig {
|
|||||||
return {
|
return {
|
||||||
name: 'Retry',
|
name: 'Retry',
|
||||||
icon: ResetIcon(),
|
icon: ResetIcon(),
|
||||||
|
testId: 'answer-retry',
|
||||||
handler: () => {
|
handler: () => {
|
||||||
reportResponse('result:retry');
|
reportResponse('result:retry');
|
||||||
panel.generate();
|
panel.generate();
|
||||||
@@ -123,6 +125,7 @@ export function createInsertItems<T extends keyof BlockSuitePresets.AIActions>(
|
|||||||
icon: html`<div style=${styleMap({ height: '20px', width: '20px' })}>
|
icon: html`<div style=${styleMap({ height: '20px', width: '20px' })}>
|
||||||
${LightLoadingIcon}
|
${LightLoadingIcon}
|
||||||
</div>`,
|
</div>`,
|
||||||
|
testId: 'answer-insert-below-loading',
|
||||||
showWhen: () => {
|
showWhen: () => {
|
||||||
const panel = getAIPanelWidget(host);
|
const panel = getAIPanelWidget(host);
|
||||||
const data = ctx.get();
|
const data = ctx.get();
|
||||||
@@ -137,6 +140,8 @@ export function createInsertItems<T extends keyof BlockSuitePresets.AIActions>(
|
|||||||
{
|
{
|
||||||
name: buttonText,
|
name: buttonText,
|
||||||
icon: InsertBelowIcon(),
|
icon: InsertBelowIcon(),
|
||||||
|
testId:
|
||||||
|
buttonText === 'Replace' ? 'answer-replace' : `answer-insert-below`,
|
||||||
showWhen: () => {
|
showWhen: () => {
|
||||||
const panel = getAIPanelWidget(host);
|
const panel = getAIPanelWidget(host);
|
||||||
const data = ctx.get();
|
const data = ctx.get();
|
||||||
@@ -191,6 +196,7 @@ export function asCaption<T extends keyof BlockSuitePresets.AIActions>(
|
|||||||
return {
|
return {
|
||||||
name: 'Use as caption',
|
name: 'Use as caption',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
|
testId: 'answer-use-as-caption',
|
||||||
showWhen: () => {
|
showWhen: () => {
|
||||||
const panel = getAIPanelWidget(host);
|
const panel = getAIPanelWidget(host);
|
||||||
return id === 'generateCaption' && !!panel.answer;
|
return id === 'generateCaption' && !!panel.answer;
|
||||||
@@ -553,9 +559,11 @@ export function actionToResponse<T extends keyof BlockSuitePresets.AIActions>(
|
|||||||
responses: [
|
responses: [
|
||||||
{
|
{
|
||||||
name: 'Response',
|
name: 'Response',
|
||||||
|
testId: 'answer-responses',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'Continue in chat',
|
name: 'Continue in chat',
|
||||||
|
testId: 'answer-continue-in-chat',
|
||||||
icon: ChatWithAiIcon({}),
|
icon: ChatWithAiIcon({}),
|
||||||
handler: () => {
|
handler: () => {
|
||||||
reportResponse('result:continue-in-chat');
|
reportResponse('result:continue-in-chat');
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ function asCaption<T extends keyof BlockSuitePresets.AIActions>(
|
|||||||
return {
|
return {
|
||||||
name: 'Use as caption',
|
name: 'Use as caption',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
|
testId: 'answer-use-as-caption',
|
||||||
showWhen: () => {
|
showWhen: () => {
|
||||||
const panel = getAIPanelWidget(host);
|
const panel = getAIPanelWidget(host);
|
||||||
return id === 'generateCaption' && !!panel.answer;
|
return id === 'generateCaption' && !!panel.answer;
|
||||||
@@ -79,6 +80,7 @@ function createNewNote(host: EditorHost): AIItemConfig {
|
|||||||
return {
|
return {
|
||||||
name: 'Create new note',
|
name: 'Create new note',
|
||||||
icon: PageIcon(),
|
icon: PageIcon(),
|
||||||
|
testId: 'answer-create-new-note',
|
||||||
showWhen: () => {
|
showWhen: () => {
|
||||||
const panel = getAIPanelWidget(host);
|
const panel = getAIPanelWidget(host);
|
||||||
return !!panel.answer && isInsideEdgelessEditor(host);
|
return !!panel.answer && isInsideEdgelessEditor(host);
|
||||||
@@ -147,9 +149,11 @@ function buildPageResponseConfig<T extends keyof BlockSuitePresets.AIActions>(
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: 'Response',
|
name: 'Response',
|
||||||
|
testId: 'answer-responses',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'Insert below',
|
name: 'Insert below',
|
||||||
|
testId: 'answer-insert-below',
|
||||||
icon: InsertBelowIcon(),
|
icon: InsertBelowIcon(),
|
||||||
showWhen: () =>
|
showWhen: () =>
|
||||||
!!panel.answer && (!id || !INSERT_ABOVE_ACTIONS.includes(id)),
|
!!panel.answer && (!id || !INSERT_ABOVE_ACTIONS.includes(id)),
|
||||||
@@ -161,6 +165,7 @@ function buildPageResponseConfig<T extends keyof BlockSuitePresets.AIActions>(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Insert above',
|
name: 'Insert above',
|
||||||
|
testId: 'answer-insert-above',
|
||||||
icon: InsertTopIcon(),
|
icon: InsertTopIcon(),
|
||||||
showWhen: () =>
|
showWhen: () =>
|
||||||
!!panel.answer && !!id && INSERT_ABOVE_ACTIONS.includes(id),
|
!!panel.answer && !!id && INSERT_ABOVE_ACTIONS.includes(id),
|
||||||
@@ -173,6 +178,7 @@ function buildPageResponseConfig<T extends keyof BlockSuitePresets.AIActions>(
|
|||||||
asCaption(host, id),
|
asCaption(host, id),
|
||||||
{
|
{
|
||||||
name: 'Replace selection',
|
name: 'Replace selection',
|
||||||
|
testId: 'answer-replace',
|
||||||
icon: ReplaceIcon(),
|
icon: ReplaceIcon(),
|
||||||
showWhen: () =>
|
showWhen: () =>
|
||||||
!!panel.answer && !EXCLUDING_REPLACE_ACTIONS.includes(id),
|
!!panel.answer && !EXCLUDING_REPLACE_ACTIONS.includes(id),
|
||||||
@@ -187,10 +193,12 @@ function buildPageResponseConfig<T extends keyof BlockSuitePresets.AIActions>(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '',
|
name: '',
|
||||||
|
testId: 'answer-common-responses',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'Continue in chat',
|
name: 'Continue in chat',
|
||||||
icon: ChatWithAiIcon(),
|
icon: ChatWithAiIcon(),
|
||||||
|
testId: 'answer-continue-in-chat',
|
||||||
handler: () => {
|
handler: () => {
|
||||||
reportResponse('result:continue-in-chat');
|
reportResponse('result:continue-in-chat');
|
||||||
AIProvider.slots.requestOpenWithChat.next({ host });
|
AIProvider.slots.requestOpenWithChat.next({ host });
|
||||||
@@ -200,6 +208,7 @@ function buildPageResponseConfig<T extends keyof BlockSuitePresets.AIActions>(
|
|||||||
{
|
{
|
||||||
name: 'Regenerate',
|
name: 'Regenerate',
|
||||||
icon: ResetIcon(),
|
icon: ResetIcon(),
|
||||||
|
testId: 'answer-regenerate',
|
||||||
handler: () => {
|
handler: () => {
|
||||||
reportResponse('result:retry');
|
reportResponse('result:retry');
|
||||||
panel.generate();
|
panel.generate();
|
||||||
@@ -208,6 +217,7 @@ function buildPageResponseConfig<T extends keyof BlockSuitePresets.AIActions>(
|
|||||||
{
|
{
|
||||||
name: 'Discard',
|
name: 'Discard',
|
||||||
icon: DeleteIcon(),
|
icon: DeleteIcon(),
|
||||||
|
testId: 'answer-discard',
|
||||||
handler: () => {
|
handler: () => {
|
||||||
panel.discard();
|
panel.discard();
|
||||||
},
|
},
|
||||||
@@ -225,6 +235,7 @@ export function buildErrorResponseConfig(panel: AffineAIPanelWidget) {
|
|||||||
{
|
{
|
||||||
name: 'Retry',
|
name: 'Retry',
|
||||||
icon: ResetIcon(),
|
icon: ResetIcon(),
|
||||||
|
testId: 'error-retry',
|
||||||
showWhen: () => true,
|
showWhen: () => true,
|
||||||
handler: () => {
|
handler: () => {
|
||||||
reportResponse('result:retry');
|
reportResponse('result:retry');
|
||||||
@@ -234,6 +245,7 @@ export function buildErrorResponseConfig(panel: AffineAIPanelWidget) {
|
|||||||
{
|
{
|
||||||
name: 'Discard',
|
name: 'Discard',
|
||||||
icon: DeleteIcon(),
|
icon: DeleteIcon(),
|
||||||
|
testId: 'error-discard',
|
||||||
showWhen: () => !!panel.answer,
|
showWhen: () => !!panel.answer,
|
||||||
handler: () => {
|
handler: () => {
|
||||||
panel.discard();
|
panel.discard();
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ export class ActionWrapper extends WithDisposable(LitElement) {
|
|||||||
<slot></slot>
|
<slot></slot>
|
||||||
<div
|
<div
|
||||||
class="action-name"
|
class="action-name"
|
||||||
|
data-testid="action-name"
|
||||||
@click=${() => (this.promptShow = !this.promptShow)}
|
@click=${() => (this.promptShow = !this.promptShow)}
|
||||||
>
|
>
|
||||||
${icons[item.action] ? icons[item.action] : DoneIcon()}
|
${icons[item.action] ? icons[item.action] : DoneIcon()}
|
||||||
@@ -152,22 +153,27 @@ export class ActionWrapper extends WithDisposable(LitElement) {
|
|||||||
</div>
|
</div>
|
||||||
${this.promptShow
|
${this.promptShow
|
||||||
? html`
|
? html`
|
||||||
<div class="answer-prompt">
|
<div class="answer-prompt" data-testid="answer-prompt">
|
||||||
<div class="subtitle">Answer</div>
|
<div class="subtitle">Answer</div>
|
||||||
${HISTORY_IMAGE_ACTIONS.includes(item.action)
|
${HISTORY_IMAGE_ACTIONS.includes(item.action)
|
||||||
? images &&
|
? images &&
|
||||||
html`<chat-content-images
|
html`<chat-content-images
|
||||||
.images=${images}
|
.images=${images}
|
||||||
|
data-testid="generated-image"
|
||||||
></chat-content-images>`
|
></chat-content-images>`
|
||||||
: nothing}
|
: nothing}
|
||||||
${answer
|
${answer
|
||||||
? createTextRenderer(this.host, { customHeading: true })(answer)
|
? createTextRenderer(this.host, {
|
||||||
|
customHeading: true,
|
||||||
|
testId: 'chat-message-action-answer',
|
||||||
|
})(answer)
|
||||||
: nothing}
|
: nothing}
|
||||||
${originalText
|
${originalText
|
||||||
? html`<div class="subtitle prompt">Prompt</div>
|
? html`<div class="subtitle prompt">Prompt</div>
|
||||||
${createTextRenderer(this.host, { customHeading: true })(
|
${createTextRenderer(this.host, {
|
||||||
item.messages[0].content + originalText
|
customHeading: true,
|
||||||
)}`
|
testId: 'chat-message-action-prompt',
|
||||||
|
})(item.messages[0].content + originalText)}`
|
||||||
: nothing}
|
: nothing}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
import './action-wrapper';
|
|
||||||
import '../content/images';
|
|
||||||
|
|
||||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import type { EditorHost } from '@blocksuite/affine/std';
|
import type { EditorHost } from '@blocksuite/affine/std';
|
||||||
import { ShadowlessElement } from '@blocksuite/affine/std';
|
import { ShadowlessElement } from '@blocksuite/affine/std';
|
||||||
@@ -27,7 +24,10 @@ export class ActionImageToText extends WithDisposable(ShadowlessElement) {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
${answer
|
${answer
|
||||||
? html`<chat-content-images .images=${answer}></chat-content-images>`
|
? html`<chat-content-images
|
||||||
|
data-testid="original-images"
|
||||||
|
.images=${answer}
|
||||||
|
></chat-content-images>`
|
||||||
: nothing}
|
: nothing}
|
||||||
</div>
|
</div>
|
||||||
</action-wrapper>`;
|
</action-wrapper>`;
|
||||||
|
|||||||
@@ -17,13 +17,19 @@ export class ActionImage extends WithDisposable(ShadowlessElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor host!: EditorHost;
|
accessor host!: EditorHost;
|
||||||
|
|
||||||
|
@property({ attribute: 'data-testid', reflect: true })
|
||||||
|
accessor testId = 'action-image';
|
||||||
|
|
||||||
protected override render() {
|
protected override render() {
|
||||||
const images = this.item.messages[0].attachments;
|
const images = this.item.messages[0].attachments;
|
||||||
|
|
||||||
return html`<action-wrapper .host=${this.host} .item=${this.item}>
|
return html`<action-wrapper .host=${this.host} .item=${this.item}>
|
||||||
<div style=${styleMap({ marginBottom: '12px' })}>
|
<div style=${styleMap({ marginBottom: '12px' })}>
|
||||||
${images
|
${images
|
||||||
? html`<chat-content-images .images=${images}></chat-content-images>`
|
? html`<chat-content-images
|
||||||
|
.images=${images}
|
||||||
|
data-testid="original-image"
|
||||||
|
></chat-content-images>`
|
||||||
: nothing}
|
: nothing}
|
||||||
</div>
|
</div>
|
||||||
</action-wrapper>`;
|
</action-wrapper>`;
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export class ActionText extends WithDisposable(LitElement) {
|
|||||||
border: isCode ? 'none' : '1px solid var(--affine-border-color)',
|
border: isCode ? 'none' : '1px solid var(--affine-border-color)',
|
||||||
})}
|
})}
|
||||||
class="original-text"
|
class="original-text"
|
||||||
|
data-testid="original-text"
|
||||||
>
|
>
|
||||||
${createTextRenderer(this.host, {
|
${createTextRenderer(this.host, {
|
||||||
customHeading: true,
|
customHeading: true,
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ export class AILoading extends WithDisposable(LitElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor stopGenerating!: () => void;
|
accessor stopGenerating!: () => void;
|
||||||
|
|
||||||
|
@property({ attribute: 'data-testid', reflect: true })
|
||||||
|
accessor testId = 'ai-loading';
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
return html`
|
return html`
|
||||||
<div class="generating-tip">
|
<div class="generating-tip">
|
||||||
|
|||||||
@@ -99,6 +99,9 @@ export class ChatPanelChips extends SignalWatcher(
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor searchMenuConfig!: SearchMenuConfig;
|
accessor searchMenuConfig!: SearchMenuConfig;
|
||||||
|
|
||||||
|
@property({ attribute: 'data-testid', reflect: true })
|
||||||
|
accessor testId = 'chat-panel-chips';
|
||||||
|
|
||||||
@query('.add-button')
|
@query('.add-button')
|
||||||
accessor addButton!: HTMLDivElement;
|
accessor addButton!: HTMLDivElement;
|
||||||
|
|
||||||
@@ -137,7 +140,11 @@ export class ChatPanelChips extends SignalWatcher(
|
|||||||
const chips = isCollapsed ? allChips.slice(0, 1) : allChips;
|
const chips = isCollapsed ? allChips.slice(0, 1) : allChips;
|
||||||
|
|
||||||
return html`<div class="chips-wrapper">
|
return html`<div class="chips-wrapper">
|
||||||
<div class="add-button" @click=${this._toggleAddDocMenu}>
|
<div
|
||||||
|
class="add-button"
|
||||||
|
data-testid="chat-panel-with-button"
|
||||||
|
@click=${this._toggleAddDocMenu}
|
||||||
|
>
|
||||||
${PlusIcon()}
|
${PlusIcon()}
|
||||||
</div>
|
</div>
|
||||||
${repeat(
|
${repeat(
|
||||||
|
|||||||
@@ -220,6 +220,9 @@ export class ChatPanelInput extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor docDisplayConfig!: DocDisplayConfig;
|
accessor docDisplayConfig!: DocDisplayConfig;
|
||||||
|
|
||||||
|
@property({ attribute: 'data-testid', reflect: true })
|
||||||
|
accessor testId = 'chat-panel-input-container';
|
||||||
|
|
||||||
private get _isNetworkActive() {
|
private get _isNetworkActive() {
|
||||||
return (
|
return (
|
||||||
!!this.networkSearchConfig.visible.value &&
|
!!this.networkSearchConfig.visible.value &&
|
||||||
@@ -335,7 +338,10 @@ export class ChatPanelInput extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
${this.chatContextValue.quote
|
${this.chatContextValue.quote
|
||||||
? html`<div class="chat-selection-quote">
|
? html`<div
|
||||||
|
class="chat-selection-quote"
|
||||||
|
data-testid="chat-selection-quote"
|
||||||
|
>
|
||||||
${repeat(
|
${repeat(
|
||||||
getFirstTwoLines(this.chatContextValue.quote),
|
getFirstTwoLines(this.chatContextValue.quote),
|
||||||
line => line,
|
line => line,
|
||||||
@@ -420,6 +426,7 @@ export class ChatPanelInput extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
: nothing}
|
: nothing}
|
||||||
${images.length < MaximumImageCount
|
${images.length < MaximumImageCount
|
||||||
? html`<div
|
? html`<div
|
||||||
|
data-testid="chat-panel-input-image-upload"
|
||||||
class="image-upload"
|
class="image-upload"
|
||||||
aria-disabled=${uploadDisabled}
|
aria-disabled=${uploadDisabled}
|
||||||
@click=${uploadDisabled ? undefined : this._uploadImageFiles}
|
@click=${uploadDisabled ? undefined : this._uploadImageFiles}
|
||||||
@@ -434,6 +441,7 @@ export class ChatPanelInput extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
this.updateContext({ status: 'success' });
|
this.updateContext({ status: 'success' });
|
||||||
reportResponse('aborted:stop');
|
reportResponse('aborted:stop');
|
||||||
}}
|
}}
|
||||||
|
data-testid="chat-panel-stop"
|
||||||
>
|
>
|
||||||
${ChatAbortIcon}
|
${ChatAbortIcon}
|
||||||
</div>`
|
</div>`
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-panel-messages {
|
.chat-panel-messages-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
@@ -157,9 +157,16 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor previewSpecBuilder!: SpecBuilder;
|
accessor previewSpecBuilder!: SpecBuilder;
|
||||||
|
|
||||||
@query('.chat-panel-messages')
|
@query('.chat-panel-messages-container')
|
||||||
accessor messagesContainer: HTMLDivElement | null = null;
|
accessor messagesContainer: HTMLDivElement | null = null;
|
||||||
|
|
||||||
|
@property({
|
||||||
|
type: String,
|
||||||
|
attribute: 'data-testid',
|
||||||
|
reflect: true,
|
||||||
|
})
|
||||||
|
accessor testId = 'chat-panel-messages';
|
||||||
|
|
||||||
getScrollContainer(): HTMLDivElement | null {
|
getScrollContainer(): HTMLDivElement | null {
|
||||||
return this.messagesContainer;
|
return this.messagesContainer;
|
||||||
}
|
}
|
||||||
@@ -168,12 +175,13 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
|
|||||||
return this.isLoading ||
|
return this.isLoading ||
|
||||||
!this.host?.doc.get(FeatureFlagService).getFlag('enable_ai_onboarding')
|
!this.host?.doc.get(FeatureFlagService).getFlag('enable_ai_onboarding')
|
||||||
? nothing
|
? nothing
|
||||||
: html`<div class="onboarding-wrapper">
|
: html`<div class="onboarding-wrapper" data-testid="ai-onboarding">
|
||||||
${repeat(
|
${repeat(
|
||||||
AIPreloadConfig,
|
AIPreloadConfig,
|
||||||
config => config.text,
|
config => config.text,
|
||||||
config => {
|
config => {
|
||||||
return html`<div
|
return html`<div
|
||||||
|
data-testid=${config.testId}
|
||||||
@click=${() => config.handler()}
|
@click=${() => config.handler()}
|
||||||
class="onboarding-item"
|
class="onboarding-item"
|
||||||
>
|
>
|
||||||
@@ -220,7 +228,8 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
class="chat-panel-messages"
|
class="chat-panel-messages-container"
|
||||||
|
data-testid="chat-panel-messages-container"
|
||||||
@scroll=${() => this._debouncedOnScroll()}
|
@scroll=${() => this._debouncedOnScroll()}
|
||||||
>
|
>
|
||||||
${filteredItems.length === 0
|
${filteredItems.length === 0
|
||||||
@@ -232,8 +241,12 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
|
|||||||
)}
|
)}
|
||||||
<div class="messages-placeholder-title" data-loading=${isLoading}>
|
<div class="messages-placeholder-title" data-loading=${isLoading}>
|
||||||
${this.isLoading
|
${this.isLoading
|
||||||
? 'AFFiNE AI is loading history...'
|
? html`<span data-testid="chat-panel-loading-state"
|
||||||
: 'What can I help you with?'}
|
>AFFiNE AI is loading history...</span
|
||||||
|
>`
|
||||||
|
: html`<span data-testid="chat-panel-empty-state"
|
||||||
|
>What can I help you with?</span
|
||||||
|
>`}
|
||||||
</div>
|
</div>
|
||||||
${this._renderAIOnboarding()}
|
${this._renderAIOnboarding()}
|
||||||
</div> `
|
</div> `
|
||||||
@@ -268,7 +281,11 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
${showDownIndicator && filteredItems.length > 0
|
${showDownIndicator && filteredItems.length > 0
|
||||||
? html`<div class="down-indicator" @click=${this._onDownIndicatorClick}>
|
? html`<div
|
||||||
|
data-testid="chat-panel-scroll-down-indicator"
|
||||||
|
class="down-indicator"
|
||||||
|
@click=${this._onDownIndicatorClick}
|
||||||
|
>
|
||||||
${ArrowDownIcon()}
|
${ArrowDownIcon()}
|
||||||
</div>`
|
</div>`
|
||||||
: nothing}
|
: nothing}
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ export type MenuItem = {
|
|||||||
name: string | TemplateResult<1>;
|
name: string | TemplateResult<1>;
|
||||||
icon: TemplateResult<1>;
|
icon: TemplateResult<1>;
|
||||||
action: MenuAction;
|
action: MenuAction;
|
||||||
|
suffix?: string | TemplateResult<1>;
|
||||||
|
testId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MenuAction = () => Promise<void> | void;
|
export type MenuAction = () => Promise<void> | void;
|
||||||
@@ -140,6 +142,7 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
{
|
{
|
||||||
key: 'tags',
|
key: 'tags',
|
||||||
name: 'Tags',
|
name: 'Tags',
|
||||||
|
testId: 'ai-chat-with-tags',
|
||||||
icon: TagsIcon(),
|
icon: TagsIcon(),
|
||||||
action: () => {
|
action: () => {
|
||||||
this._toggleMode(AddPopoverMode.Tags);
|
this._toggleMode(AddPopoverMode.Tags);
|
||||||
@@ -148,6 +151,7 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
{
|
{
|
||||||
key: 'collections',
|
key: 'collections',
|
||||||
name: 'Collections',
|
name: 'Collections',
|
||||||
|
testId: 'ai-chat-with-collections',
|
||||||
icon: CollectionsIcon(),
|
icon: CollectionsIcon(),
|
||||||
action: () => {
|
action: () => {
|
||||||
this._toggleMode(AddPopoverMode.Collections);
|
this._toggleMode(AddPopoverMode.Collections);
|
||||||
@@ -176,6 +180,7 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
{
|
{
|
||||||
key: 'files',
|
key: 'files',
|
||||||
name: 'Upload files (pdf, txt, csv)',
|
name: 'Upload files (pdf, txt, csv)',
|
||||||
|
testId: 'ai-chat-with-files',
|
||||||
icon: UploadIcon(),
|
icon: UploadIcon(),
|
||||||
action: this._addFileChip,
|
action: this._addFileChip,
|
||||||
},
|
},
|
||||||
@@ -330,13 +335,14 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
${repeat(
|
${repeat(
|
||||||
items,
|
items,
|
||||||
item => item.key,
|
item => item.key,
|
||||||
({ key, name, icon, action }, idx) => {
|
({ key, name, icon, action, testId }, idx) => {
|
||||||
const curIdx = startIndex + idx;
|
const curIdx = startIndex + idx;
|
||||||
return html`<icon-button
|
return html`<icon-button
|
||||||
width="280px"
|
width="280px"
|
||||||
height="30px"
|
height="30px"
|
||||||
data-id=${key}
|
data-id=${key}
|
||||||
data-index=${curIdx}
|
data-index=${curIdx}
|
||||||
|
data-testid=${testId}
|
||||||
.text=${name}
|
.text=${name}
|
||||||
hover=${this._activatedIndex === curIdx}
|
hover=${this._activatedIndex === curIdx}
|
||||||
@click=${() => action()?.catch(console.error)}
|
@click=${() => action()?.catch(console.error)}
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ export class ChatContentPureText extends ShadowlessElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor text: string = '';
|
accessor text: string = '';
|
||||||
|
|
||||||
|
@property({ attribute: 'data-testid', reflect: true })
|
||||||
|
accessor testId = 'chat-content-pure-text';
|
||||||
|
|
||||||
protected override render() {
|
protected override render() {
|
||||||
return this.text.length > 0
|
return this.text.length > 0
|
||||||
? html`<div class="chat-content-pure-text">${this.text}</div>`
|
? html`<div class="chat-content-pure-text">${this.text}</div>`
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ export class ChatMessageAction extends WithDisposable(ShadowlessElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor item!: ChatAction;
|
accessor item!: ChatAction;
|
||||||
|
|
||||||
|
@property({ attribute: 'data-testid', reflect: true })
|
||||||
|
accessor testId = 'chat-message-action';
|
||||||
|
|
||||||
renderHeader() {
|
renderHeader() {
|
||||||
return html`
|
return html`
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor isLast: boolean = false;
|
accessor isLast: boolean = false;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: 'data-status', reflect: true })
|
||||||
accessor status: string = 'idle';
|
accessor status: string = 'idle';
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
@@ -49,6 +49,9 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor retry!: () => void;
|
accessor retry!: () => void;
|
||||||
|
|
||||||
|
@property({ attribute: 'data-testid', reflect: true })
|
||||||
|
accessor testId = 'chat-message-assistant';
|
||||||
|
|
||||||
renderHeader() {
|
renderHeader() {
|
||||||
const isWithDocs =
|
const isWithDocs =
|
||||||
'content' in this.item &&
|
'content' in this.item &&
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ export class ChatMessageUser extends WithDisposable(ShadowlessElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor item!: ChatMessage;
|
accessor item!: ChatMessage;
|
||||||
|
|
||||||
|
@property({ attribute: 'data-testid', reflect: true })
|
||||||
|
accessor testId = 'chat-message-user';
|
||||||
|
|
||||||
renderContent() {
|
renderContent() {
|
||||||
const { item } = this;
|
const { item } = this;
|
||||||
|
|
||||||
@@ -41,7 +44,7 @@ export class ChatMessageUser extends WithDisposable(ShadowlessElement) {
|
|||||||
.images=${item.attachments}
|
.images=${item.attachments}
|
||||||
></chat-content-images>`
|
></chat-content-images>`
|
||||||
: nothing}
|
: nothing}
|
||||||
<div class="text-content-wrapper">
|
<div class="text-content-wrapper" data-test-id="chat-content-user-text">
|
||||||
<chat-content-pure-text .text=${item.content}></chat-content-pure-text>
|
<chat-content-pure-text .text=${item.content}></chat-content-pure-text>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export const AIPreloadConfig = [
|
|||||||
{
|
{
|
||||||
icon: LanguageIcon(),
|
icon: LanguageIcon(),
|
||||||
text: 'Read a foreign language article with AI',
|
text: 'Read a foreign language article with AI',
|
||||||
|
testId: 'read-foreign-language-article-with-ai',
|
||||||
handler: () => {
|
handler: () => {
|
||||||
AIProvider.slots.requestInsertTemplate.next({
|
AIProvider.slots.requestInsertTemplate.next({
|
||||||
template: readAforeign,
|
template: readAforeign,
|
||||||
@@ -27,6 +28,7 @@ export const AIPreloadConfig = [
|
|||||||
{
|
{
|
||||||
icon: MindmapIcon(),
|
icon: MindmapIcon(),
|
||||||
text: 'Tidy an article with AI MindMap Action',
|
text: 'Tidy an article with AI MindMap Action',
|
||||||
|
testId: 'tidy-an-article-with-ai-mindmap-action',
|
||||||
handler: () => {
|
handler: () => {
|
||||||
AIProvider.slots.requestInsertTemplate.next({
|
AIProvider.slots.requestInsertTemplate.next({
|
||||||
template: TidyMindMapV3,
|
template: TidyMindMapV3,
|
||||||
@@ -37,6 +39,7 @@ export const AIPreloadConfig = [
|
|||||||
{
|
{
|
||||||
icon: ImageIcon(),
|
icon: ImageIcon(),
|
||||||
text: 'Add illustrations to the article',
|
text: 'Add illustrations to the article',
|
||||||
|
testId: 'add-illustrations-to-the-article',
|
||||||
handler: () => {
|
handler: () => {
|
||||||
AIProvider.slots.requestInsertTemplate.next({
|
AIProvider.slots.requestInsertTemplate.next({
|
||||||
template: redHat,
|
template: redHat,
|
||||||
@@ -47,6 +50,7 @@ export const AIPreloadConfig = [
|
|||||||
{
|
{
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
text: 'Complete writing with AI',
|
text: 'Complete writing with AI',
|
||||||
|
testId: 'complete-writing-with-ai',
|
||||||
handler: () => {
|
handler: () => {
|
||||||
AIProvider.slots.requestInsertTemplate.next({
|
AIProvider.slots.requestInsertTemplate.next({
|
||||||
template: completeWritingWithAI,
|
template: completeWritingWithAI,
|
||||||
@@ -57,6 +61,7 @@ export const AIPreloadConfig = [
|
|||||||
{
|
{
|
||||||
icon: SendIcon(),
|
icon: SendIcon(),
|
||||||
text: 'Freely communicate with AI',
|
text: 'Freely communicate with AI',
|
||||||
|
testId: 'freely-communicate-with-ai',
|
||||||
handler: () => {
|
handler: () => {
|
||||||
AIProvider.slots.requestInsertTemplate.next({
|
AIProvider.slots.requestInsertTemplate.next({
|
||||||
template: freelyCommunicateWithAI,
|
template: freelyCommunicateWithAI,
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ export class AIItemList extends WithDisposable(LitElement) {
|
|||||||
|
|
||||||
createLitPortal({
|
createLitPortal({
|
||||||
template: html`<ai-sub-item-list
|
template: html`<ai-sub-item-list
|
||||||
|
data-testid=${item.testId ? item.testId + '-menu' : ''}
|
||||||
.item=${item}
|
.item=${item}
|
||||||
.host=${this.host}
|
.host=${this.host}
|
||||||
.onClick=${this.onClick}
|
.onClick=${this.onClick}
|
||||||
@@ -141,6 +142,9 @@ export class AIItemList extends WithDisposable(LitElement) {
|
|||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor onClick: (() => void) | undefined = undefined;
|
accessor onClick: (() => void) | undefined = undefined;
|
||||||
|
|
||||||
|
@property({ attribute: 'data-testid', reflect: true })
|
||||||
|
accessor testId = 'ai-item-list';
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
@@ -23,8 +23,10 @@ export class AIItem extends WithDisposable(LitElement) {
|
|||||||
override render() {
|
override render() {
|
||||||
const { item } = this;
|
const { item } = this;
|
||||||
const className = item.name.split(' ').join('-').toLocaleLowerCase();
|
const className = item.name.split(' ').join('-').toLocaleLowerCase();
|
||||||
|
const testId = item.testId;
|
||||||
|
|
||||||
return html`<div
|
return html`<div
|
||||||
|
data-testid=${testId}
|
||||||
class="menu-item ${className}"
|
class="menu-item ${className}"
|
||||||
@pointerdown=${(e: MouseEvent) => e.stopPropagation()}
|
@pointerdown=${(e: MouseEvent) => e.stopPropagation()}
|
||||||
@click=${() => {
|
@click=${() => {
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export class AISubItemList extends WithDisposable(LitElement) {
|
|||||||
subItem => subItem.type,
|
subItem => subItem.type,
|
||||||
subItem =>
|
subItem =>
|
||||||
html`<div
|
html`<div
|
||||||
|
data-testid=${subItem.testId}
|
||||||
class="menu-item"
|
class="menu-item"
|
||||||
@click=${() => this._handleClick(subItem)}
|
@click=${() => this._handleClick(subItem)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import type { TemplateResult } from 'lit';
|
|||||||
|
|
||||||
export interface AIItemGroupConfig {
|
export interface AIItemGroupConfig {
|
||||||
name?: string;
|
name?: string;
|
||||||
|
testId?: string;
|
||||||
items: AIItemConfig[];
|
items: AIItemConfig[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AIItemConfig {
|
export interface AIItemConfig {
|
||||||
name: string;
|
name: string;
|
||||||
|
testId: string;
|
||||||
icon: TemplateResult | (() => HTMLElement);
|
icon: TemplateResult | (() => HTMLElement);
|
||||||
showWhen?: (
|
showWhen?: (
|
||||||
chain: Chain<InitCommandCtx>,
|
chain: Chain<InitCommandCtx>,
|
||||||
@@ -23,6 +25,7 @@ export interface AIItemConfig {
|
|||||||
|
|
||||||
export interface AISubItemConfig {
|
export interface AISubItemConfig {
|
||||||
type: string;
|
type: string;
|
||||||
|
testId?: string;
|
||||||
handler?: (host: EditorHost) => void;
|
handler?: (host: EditorHost) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ export class AskAIButton extends WithDisposable(LitElement) {
|
|||||||
});
|
});
|
||||||
return html`<div
|
return html`<div
|
||||||
class="ask-ai-button"
|
class="ask-ai-button"
|
||||||
|
data-testid="ask-ai-button"
|
||||||
style=${buttonStyles}
|
style=${buttonStyles}
|
||||||
${toggleType === 'hover' ? ref(this._whenHover.setReference) : nothing}
|
${toggleType === 'hover' ? ref(this._whenHover.setReference) : nothing}
|
||||||
@click=${this._toggleAIPanel}
|
@click=${this._toggleAIPanel}
|
||||||
|
|||||||
@@ -110,7 +110,11 @@ export class AskAIToolbarButton extends WithDisposable(LitElement) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
return html`<div class="ask-ai-button" @click=${this._onClick}>
|
return html`<div
|
||||||
|
class="ask-ai-button"
|
||||||
|
data-testid="ask-ai-button"
|
||||||
|
@click=${this._onClick}
|
||||||
|
>
|
||||||
<ask-ai-icon .size=${'middle'}></ask-ai-icon>
|
<ask-ai-icon .size=${'middle'}></ask-ai-icon>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,9 @@ export class ChatActionList extends LitElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor withMargin = false;
|
accessor withMargin = false;
|
||||||
|
|
||||||
|
@property({ attribute: 'data-testid', reflect: true })
|
||||||
|
accessor testId = 'chat-action-list';
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
const { actions } = this;
|
const { actions } = this;
|
||||||
if (!actions.length) {
|
if (!actions.length) {
|
||||||
|
|||||||
@@ -127,6 +127,9 @@ export class ChatCopyMore extends WithDisposable(LitElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor retry = () => {};
|
accessor retry = () => {};
|
||||||
|
|
||||||
|
@property({ attribute: 'data-testid', reflect: true })
|
||||||
|
accessor testId = 'chat-actions';
|
||||||
|
|
||||||
private _toggle() {
|
private _toggle() {
|
||||||
this._morePopper?.toggle();
|
this._morePopper?.toggle();
|
||||||
}
|
}
|
||||||
@@ -197,7 +200,11 @@ export class ChatCopyMore extends WithDisposable(LitElement) {
|
|||||||
: nothing}
|
: nothing}
|
||||||
${isLast
|
${isLast
|
||||||
? nothing
|
? nothing
|
||||||
: html`<div class="button more" @click=${this._toggle}>
|
: html`<div
|
||||||
|
class="button more"
|
||||||
|
data-testid="action-more-button"
|
||||||
|
@click=${this._toggle}
|
||||||
|
>
|
||||||
${MoreHorizontalIcon({ width: '20px', height: '20px' })}
|
${MoreHorizontalIcon({ width: '20px', height: '20px' })}
|
||||||
</div> `}
|
</div> `}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export type TextRendererOptions = {
|
|||||||
customHeading?: boolean;
|
customHeading?: boolean;
|
||||||
extensions?: ExtensionType[];
|
extensions?: ExtensionType[];
|
||||||
additionalMiddlewares?: TransformerMiddleware[];
|
additionalMiddlewares?: TransformerMiddleware[];
|
||||||
|
testId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CustomPageEditorBlockSpecs: ExtensionType[] = [
|
export const CustomPageEditorBlockSpecs: ExtensionType[] = [
|
||||||
@@ -290,13 +291,13 @@ export class TextRenderer extends WithDisposable(ShadowlessElement) {
|
|||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { customHeading } = this.options;
|
const { customHeading, testId } = this.options;
|
||||||
const classes = classMap({
|
const classes = classMap({
|
||||||
'text-renderer-container': true,
|
'text-renderer-container': true,
|
||||||
'custom-heading': !!customHeading,
|
'custom-heading': !!customHeading,
|
||||||
});
|
});
|
||||||
return html`
|
return html`
|
||||||
<div class=${classes}>
|
<div class=${classes} data-testid=${testId}>
|
||||||
${keyed(
|
${keyed(
|
||||||
this._doc,
|
this._doc,
|
||||||
html`<div class="ai-answer-text-editor affine-page-viewport">
|
html`<div class="ai-answer-text-editor affine-page-viewport">
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ import {
|
|||||||
const translateSubItem = translateLangs.map(lang => {
|
const translateSubItem = translateLangs.map(lang => {
|
||||||
return {
|
return {
|
||||||
type: lang,
|
type: lang,
|
||||||
|
testId: `action-translate-${lang}`,
|
||||||
handler: actionToHandler('translate', AIStarIconWithAnimation, { lang }),
|
handler: actionToHandler('translate', AIStarIconWithAnimation, { lang }),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -68,6 +69,7 @@ const translateSubItem = translateLangs.map(lang => {
|
|||||||
const toneSubItem = textTones.map(tone => {
|
const toneSubItem = textTones.map(tone => {
|
||||||
return {
|
return {
|
||||||
type: tone,
|
type: tone,
|
||||||
|
testId: `action-change-tone-${tone.toLowerCase()}`,
|
||||||
handler: actionToHandler('changeTone', AIStarIconWithAnimation, { tone }),
|
handler: actionToHandler('changeTone', AIStarIconWithAnimation, { tone }),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -75,6 +77,7 @@ const toneSubItem = textTones.map(tone => {
|
|||||||
export const imageFilterSubItem = imageFilterStyles.map(style => {
|
export const imageFilterSubItem = imageFilterStyles.map(style => {
|
||||||
return {
|
return {
|
||||||
type: style,
|
type: style,
|
||||||
|
testId: `action-image-filter-${style.toLowerCase().replace(' ', '-')}`,
|
||||||
handler: actionToHandler(
|
handler: actionToHandler(
|
||||||
'filterImage',
|
'filterImage',
|
||||||
AIImageIconWithAnimation,
|
AIImageIconWithAnimation,
|
||||||
@@ -89,6 +92,7 @@ export const imageFilterSubItem = imageFilterStyles.map(style => {
|
|||||||
export const imageProcessingSubItem = imageProcessingTypes.map(type => {
|
export const imageProcessingSubItem = imageProcessingTypes.map(type => {
|
||||||
return {
|
return {
|
||||||
type,
|
type,
|
||||||
|
testId: `action-image-processing-${type.toLowerCase().replace(' ', '-')}`,
|
||||||
handler: actionToHandler(
|
handler: actionToHandler(
|
||||||
'processImage',
|
'processImage',
|
||||||
AIImageIconWithAnimation,
|
AIImageIconWithAnimation,
|
||||||
@@ -105,6 +109,7 @@ const othersGroup: AIItemGroupConfig = {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'Continue with AI',
|
name: 'Continue with AI',
|
||||||
|
testId: 'action-continue-with-ai',
|
||||||
icon: CommentIcon({ width: '20px', height: '20px' }),
|
icon: CommentIcon({ width: '20px', height: '20px' }),
|
||||||
showWhen: () => true,
|
showWhen: () => true,
|
||||||
handler: host => {
|
handler: host => {
|
||||||
@@ -125,18 +130,21 @@ const editGroup: AIItemGroupConfig = {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'Translate to',
|
name: 'Translate to',
|
||||||
|
testId: 'action-translate',
|
||||||
icon: LanguageIcon(),
|
icon: LanguageIcon(),
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
subItem: translateSubItem,
|
subItem: translateSubItem,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Change tone to',
|
name: 'Change tone to',
|
||||||
|
testId: 'action-change-tone',
|
||||||
icon: ToneIcon(),
|
icon: ToneIcon(),
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
subItem: toneSubItem,
|
subItem: toneSubItem,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Improve writing',
|
name: 'Improve writing',
|
||||||
|
testId: 'action-improve-writing',
|
||||||
icon: ImproveWritingIcon(),
|
icon: ImproveWritingIcon(),
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
handler: actionToHandler('improveWriting', AIStarIconWithAnimation),
|
handler: actionToHandler('improveWriting', AIStarIconWithAnimation),
|
||||||
@@ -144,18 +152,21 @@ const editGroup: AIItemGroupConfig = {
|
|||||||
|
|
||||||
{
|
{
|
||||||
name: 'Make it longer',
|
name: 'Make it longer',
|
||||||
|
testId: 'action-make-it-longer',
|
||||||
icon: LongerIcon(),
|
icon: LongerIcon(),
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
handler: actionToHandler('makeLonger', AIStarIconWithAnimation),
|
handler: actionToHandler('makeLonger', AIStarIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Make it shorter',
|
name: 'Make it shorter',
|
||||||
|
testId: 'action-make-it-shorter',
|
||||||
icon: ShorterIcon(),
|
icon: ShorterIcon(),
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
handler: actionToHandler('makeShorter', AIStarIconWithAnimation),
|
handler: actionToHandler('makeShorter', AIStarIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Continue writing',
|
name: 'Continue writing',
|
||||||
|
testId: 'action-continue-writing',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
handler: actionToHandler('continueWriting', AIPenIconWithAnimation),
|
handler: actionToHandler('continueWriting', AIPenIconWithAnimation),
|
||||||
@@ -168,30 +179,35 @@ const draftGroup: AIItemGroupConfig = {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'Write an article about this',
|
name: 'Write an article about this',
|
||||||
|
testId: 'action-write-article',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
handler: actionToHandler('writeArticle', AIPenIconWithAnimation),
|
handler: actionToHandler('writeArticle', AIPenIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Write a tweet about this',
|
name: 'Write a tweet about this',
|
||||||
|
testId: 'action-write-twitter-post',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
handler: actionToHandler('writeTwitterPost', AIPenIconWithAnimation),
|
handler: actionToHandler('writeTwitterPost', AIPenIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Write a poem about this',
|
name: 'Write a poem about this',
|
||||||
|
testId: 'action-write-poem',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
handler: actionToHandler('writePoem', AIPenIconWithAnimation),
|
handler: actionToHandler('writePoem', AIPenIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Write a blog post about this',
|
name: 'Write a blog post about this',
|
||||||
|
testId: 'action-write-blog-post',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
handler: actionToHandler('writeBlogPost', AIPenIconWithAnimation),
|
handler: actionToHandler('writeBlogPost', AIPenIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Brainstorm ideas about this',
|
name: 'Brainstorm ideas about this',
|
||||||
|
testId: 'action-brainstorm',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
handler: actionToHandler('brainstorm', AIPenIconWithAnimation),
|
handler: actionToHandler('brainstorm', AIPenIconWithAnimation),
|
||||||
@@ -205,18 +221,21 @@ const reviewGroup: AIItemGroupConfig = {
|
|||||||
{
|
{
|
||||||
name: 'Fix spelling',
|
name: 'Fix spelling',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
|
testId: 'action-fix-spelling',
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
handler: actionToHandler('fixSpelling', AIStarIconWithAnimation),
|
handler: actionToHandler('fixSpelling', AIStarIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Fix grammar',
|
name: 'Fix grammar',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
|
testId: 'action-fix-grammar',
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
handler: actionToHandler('improveGrammar', AIStarIconWithAnimation),
|
handler: actionToHandler('improveGrammar', AIStarIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Explain this image',
|
name: 'Explain this image',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
|
testId: 'action-explain-image',
|
||||||
showWhen: imageOnlyShowWhen,
|
showWhen: imageOnlyShowWhen,
|
||||||
handler: actionToHandler(
|
handler: actionToHandler(
|
||||||
'explainImage',
|
'explainImage',
|
||||||
@@ -228,18 +247,21 @@ const reviewGroup: AIItemGroupConfig = {
|
|||||||
{
|
{
|
||||||
name: 'Explain this code',
|
name: 'Explain this code',
|
||||||
icon: ExplainIcon(),
|
icon: ExplainIcon(),
|
||||||
|
testId: 'action-explain-code',
|
||||||
showWhen: noteWithCodeBlockShowWen,
|
showWhen: noteWithCodeBlockShowWen,
|
||||||
handler: actionToHandler('explainCode', AIStarIconWithAnimation),
|
handler: actionToHandler('explainCode', AIStarIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Check code error',
|
name: 'Check code error',
|
||||||
icon: ExplainIcon(),
|
icon: ExplainIcon(),
|
||||||
|
testId: 'action-check-code-error',
|
||||||
showWhen: noteWithCodeBlockShowWen,
|
showWhen: noteWithCodeBlockShowWen,
|
||||||
handler: actionToHandler('checkCodeErrors', AIStarIconWithAnimation),
|
handler: actionToHandler('checkCodeErrors', AIStarIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Explain selection',
|
name: 'Explain selection',
|
||||||
icon: SelectionIcon({ width: '20px', height: '20px' }),
|
icon: SelectionIcon({ width: '20px', height: '20px' }),
|
||||||
|
testId: 'action-explain-selection',
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
handler: actionToHandler('explain', AIStarIconWithAnimation),
|
handler: actionToHandler('explain', AIStarIconWithAnimation),
|
||||||
},
|
},
|
||||||
@@ -252,19 +274,22 @@ const generateGroup: AIItemGroupConfig = {
|
|||||||
{
|
{
|
||||||
name: 'Summarize',
|
name: 'Summarize',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
|
testId: 'action-summarize',
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
handler: actionToHandler('summary', AIPenIconWithAnimation),
|
handler: actionToHandler('summary', AIPenIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Generate headings',
|
name: 'Generate headings',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
handler: actionToHandler('createHeadings', AIPenIconWithAnimation),
|
testId: 'action-generate-headings',
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
|
handler: actionToHandler('createHeadings', AIPenIconWithAnimation),
|
||||||
beta: true,
|
beta: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Generate an image',
|
name: 'Generate an image',
|
||||||
icon: ImageIcon(),
|
icon: ImageIcon(),
|
||||||
|
testId: 'action-generate-image',
|
||||||
showWhen: notAllAIChatBlockShowWhen,
|
showWhen: notAllAIChatBlockShowWhen,
|
||||||
handler: actionToHandler(
|
handler: actionToHandler(
|
||||||
'createImage',
|
'createImage',
|
||||||
@@ -339,12 +364,14 @@ const generateGroup: AIItemGroupConfig = {
|
|||||||
{
|
{
|
||||||
name: 'Generate outline',
|
name: 'Generate outline',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
|
testId: 'action-generate-outline',
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
handler: actionToHandler('writeOutline', AIPenIconWithAnimation),
|
handler: actionToHandler('writeOutline', AIPenIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Expand from this mind map node',
|
name: 'Expand from this mind map node',
|
||||||
icon: MindmapNodeIcon(),
|
icon: MindmapNodeIcon(),
|
||||||
|
testId: 'action-expand-mindmap-node',
|
||||||
showWhen: mindmapChildShowWhen,
|
showWhen: mindmapChildShowWhen,
|
||||||
handler: actionToHandler(
|
handler: actionToHandler(
|
||||||
'expandMindmap',
|
'expandMindmap',
|
||||||
@@ -370,12 +397,14 @@ const generateGroup: AIItemGroupConfig = {
|
|||||||
{
|
{
|
||||||
name: 'Brainstorm ideas with mind map',
|
name: 'Brainstorm ideas with mind map',
|
||||||
icon: MindmapIcon(),
|
icon: MindmapIcon(),
|
||||||
|
testId: 'action-brainstorm-mindmap',
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
handler: actionToHandler('brainstormMindmap', AIMindMapIconWithAnimation),
|
handler: actionToHandler('brainstormMindmap', AIMindMapIconWithAnimation),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Regenerate mind map',
|
name: 'Regenerate mind map',
|
||||||
icon: MindmapIcon(),
|
icon: MindmapIcon(),
|
||||||
|
testId: 'action-regenerate-mindmap',
|
||||||
showWhen: mindmapRootShowWhen,
|
showWhen: mindmapRootShowWhen,
|
||||||
handler: actionToHandler(
|
handler: actionToHandler(
|
||||||
'brainstormMindmap',
|
'brainstormMindmap',
|
||||||
@@ -388,6 +417,7 @@ const generateGroup: AIItemGroupConfig = {
|
|||||||
{
|
{
|
||||||
name: 'Generate presentation',
|
name: 'Generate presentation',
|
||||||
icon: PresentationIcon(),
|
icon: PresentationIcon(),
|
||||||
|
testId: 'action-generate-presentation',
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
handler: actionToHandler('createSlides', AIPresentationIconWithAnimation),
|
handler: actionToHandler('createSlides', AIPresentationIconWithAnimation),
|
||||||
beta: true,
|
beta: true,
|
||||||
@@ -395,6 +425,7 @@ const generateGroup: AIItemGroupConfig = {
|
|||||||
{
|
{
|
||||||
name: 'Make it real',
|
name: 'Make it real',
|
||||||
icon: MakeItRealIcon({ width: '20px', height: '20px' }),
|
icon: MakeItRealIcon({ width: '20px', height: '20px' }),
|
||||||
|
testId: 'action-make-it-real',
|
||||||
beta: true,
|
beta: true,
|
||||||
showWhen: notAllAIChatBlockShowWhen,
|
showWhen: notAllAIChatBlockShowWhen,
|
||||||
handler: actionToHandler(
|
handler: actionToHandler(
|
||||||
@@ -476,6 +507,7 @@ const generateGroup: AIItemGroupConfig = {
|
|||||||
{
|
{
|
||||||
name: 'AI image filter',
|
name: 'AI image filter',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
|
testId: 'action-ai-image-filter',
|
||||||
showWhen: imageOnlyShowWhen,
|
showWhen: imageOnlyShowWhen,
|
||||||
subItem: imageFilterSubItem,
|
subItem: imageFilterSubItem,
|
||||||
subItemOffset: [12, -4],
|
subItemOffset: [12, -4],
|
||||||
@@ -484,6 +516,7 @@ const generateGroup: AIItemGroupConfig = {
|
|||||||
{
|
{
|
||||||
name: 'Image processing',
|
name: 'Image processing',
|
||||||
icon: ImageIcon(),
|
icon: ImageIcon(),
|
||||||
|
testId: 'action-image-processing',
|
||||||
showWhen: imageOnlyShowWhen,
|
showWhen: imageOnlyShowWhen,
|
||||||
subItem: imageProcessingSubItem,
|
subItem: imageProcessingSubItem,
|
||||||
subItemOffset: [12, -6],
|
subItemOffset: [12, -6],
|
||||||
@@ -492,6 +525,7 @@ const generateGroup: AIItemGroupConfig = {
|
|||||||
{
|
{
|
||||||
name: 'Generate a caption',
|
name: 'Generate a caption',
|
||||||
icon: PenIcon(),
|
icon: PenIcon(),
|
||||||
|
testId: 'action-generate-caption',
|
||||||
showWhen: imageOnlyShowWhen,
|
showWhen: imageOnlyShowWhen,
|
||||||
beta: true,
|
beta: true,
|
||||||
handler: actionToHandler(
|
handler: actionToHandler(
|
||||||
@@ -504,6 +538,7 @@ const generateGroup: AIItemGroupConfig = {
|
|||||||
{
|
{
|
||||||
name: 'Find actions',
|
name: 'Find actions',
|
||||||
icon: SearchIcon(),
|
icon: SearchIcon(),
|
||||||
|
testId: 'action-find-actions',
|
||||||
showWhen: noteBlockOrTextShowWhen,
|
showWhen: noteBlockOrTextShowWhen,
|
||||||
handler: actionToHandler('findActions', AIStarIconWithAnimation),
|
handler: actionToHandler('findActions', AIStarIconWithAnimation),
|
||||||
beta: true,
|
beta: true,
|
||||||
|
|||||||
@@ -181,6 +181,9 @@ export class AIErrorWrapper extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor showDetailPanel: boolean = false;
|
accessor showDetailPanel: boolean = false;
|
||||||
|
|
||||||
|
@property({ attribute: 'data-testid', reflect: true })
|
||||||
|
accessor testId = 'ai-error';
|
||||||
}
|
}
|
||||||
|
|
||||||
const PaymentRequiredErrorRenderer = (host: EditorHost) => html`
|
const PaymentRequiredErrorRenderer = (host: EditorHost) => html`
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export const createImageRenderer: (
|
|||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="ai-answer-image">
|
<div class="ai-answer-image" data-testid="ai-answer-image">
|
||||||
<img src=${answer}></img>
|
<img src=${answer}></img>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
|
|||||||
@@ -188,12 +188,21 @@ export class ChatBlockInput extends SignalWatcher(LitElement) {
|
|||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
${images.length < MaximumImageCount
|
${images.length < MaximumImageCount
|
||||||
? html`<div class="image-upload" @click=${this._handleImageUpload}>
|
? html`<div
|
||||||
|
data-testid="chat-block-input-image-upload"
|
||||||
|
class="image-upload"
|
||||||
|
@click=${this._handleImageUpload}
|
||||||
|
>
|
||||||
${ImageIcon()}
|
${ImageIcon()}
|
||||||
</div>`
|
</div>`
|
||||||
: nothing}
|
: nothing}
|
||||||
${status === 'transmitting'
|
${status === 'transmitting'
|
||||||
? html`<div @click=${this._handleAbort}>${ChatAbortIcon}</div>`
|
? html`<div
|
||||||
|
@click=${this._handleAbort}
|
||||||
|
data-testid="chat-panel-peek-view-stop"
|
||||||
|
>
|
||||||
|
${ChatAbortIcon}
|
||||||
|
</div>`
|
||||||
: html`<div
|
: html`<div
|
||||||
@click=${this._onTextareaSend}
|
@click=${this._onTextareaSend}
|
||||||
class="chat-panel-send"
|
class="chat-panel-send"
|
||||||
|
|||||||
@@ -517,7 +517,12 @@ export class AffineAIPanelWidget extends WidgetComponent {
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return html`<div class="ai-panel-container">${mainTemplate}</div>`;
|
return html`<div
|
||||||
|
class="ai-panel-container"
|
||||||
|
data-testid="ai-panel-container"
|
||||||
|
>
|
||||||
|
${mainTemplate}
|
||||||
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override willUpdate(changed: PropertyValues): void {
|
protected override willUpdate(changed: PropertyValues): void {
|
||||||
|
|||||||
@@ -74,9 +74,12 @@ export class AIFinishTip extends WithDisposable(LitElement) {
|
|||||||
${this.copy?.allowed
|
${this.copy?.allowed
|
||||||
? html`<div class="right">
|
? html`<div class="right">
|
||||||
${this.copied
|
${this.copied
|
||||||
? html`<div class="copied">${AIDoneIcon}</div>`
|
? html`<div class="copied" data-testid="answer-copied">
|
||||||
|
${AIDoneIcon}
|
||||||
|
</div>`
|
||||||
: html`<div
|
: html`<div
|
||||||
class="copy"
|
class="copy"
|
||||||
|
data-testid="answer-copy-button"
|
||||||
@click=${async () => {
|
@click=${async () => {
|
||||||
this.copied = !!(await this.copy?.onCopy());
|
this.copied = !!(await this.copy?.onCopy());
|
||||||
if (this.copied) {
|
if (this.copied) {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export class AIPanelAnswer extends WithDisposable(LitElement) {
|
|||||||
return html`
|
return html`
|
||||||
<div class="answer">
|
<div class="answer">
|
||||||
<div class="answer-head">Answer</div>
|
<div class="answer-head">Answer</div>
|
||||||
<div class="answer-body">
|
<div class="answer-body" data-testid="answer-content">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -104,7 +104,10 @@ export class AIPanelAnswer extends WithDisposable(LitElement) {
|
|||||||
${index !== 0
|
${index !== 0
|
||||||
? html`<ai-panel-divider></ai-panel-divider>`
|
? html`<ai-panel-divider></ai-panel-divider>`
|
||||||
: nothing}
|
: nothing}
|
||||||
<div class="response-list-container">
|
<div
|
||||||
|
class="response-list-container"
|
||||||
|
data-testid=${group.testId}
|
||||||
|
>
|
||||||
<ai-item-list
|
<ai-item-list
|
||||||
.host=${this.host}
|
.host=${this.host}
|
||||||
.groups=${[group]}
|
.groups=${[group]}
|
||||||
@@ -143,6 +146,9 @@ export class AIPanelAnswer extends WithDisposable(LitElement) {
|
|||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor host!: EditorHost;
|
accessor host!: EditorHost;
|
||||||
|
|
||||||
|
@property({ attribute: 'data-testid', reflect: true })
|
||||||
|
accessor testId = 'ai-penel-answer';
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ export class AIPanelError extends WithDisposable(LitElement) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="error">
|
<div class="error" data-testid="ai-error">
|
||||||
<div class="answer-tip">
|
<div class="answer-tip">
|
||||||
<div class="answer-label">Answer</div>
|
<div class="answer-label">Answer</div>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
|||||||
@@ -85,10 +85,10 @@ export class AIPanelGenerating extends WithDisposable(LitElement) {
|
|||||||
.showHeader=${!this.withAnswer}
|
.showHeader=${!this.withAnswer}
|
||||||
></generating-placeholder>`
|
></generating-placeholder>`
|
||||||
: nothing}
|
: nothing}
|
||||||
<div class="generating-tip">
|
<div class="generating-tip" data-testid="ai-generating">
|
||||||
<div class="left">${generatingIcon}</div>
|
<div class="left">${generatingIcon}</div>
|
||||||
<div class="text">AI is generating...</div>
|
<div class="text">AI is generating...</div>
|
||||||
<div @click=${this.stopGenerating} class="right">
|
<div @click=${this.stopGenerating} class="right" data-testid="ai-stop">
|
||||||
<span class="stop-icon">${AIStopIcon}</span>
|
<span class="stop-icon">${AIStopIcon}</span>
|
||||||
<span class="esc-label">ESC</span>
|
<span class="esc-label">ESC</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ export class EdgelessCopilotToolbarEntry extends WithDisposable(LitElement) {
|
|||||||
return html`<edgeless-tool-icon-button
|
return html`<edgeless-tool-icon-button
|
||||||
aria-label="Ask AI"
|
aria-label="Ask AI"
|
||||||
class="copilot-icon-button"
|
class="copilot-icon-button"
|
||||||
|
data-testid="ask-ai-button"
|
||||||
@click=${this._onClick}
|
@click=${this._onClick}
|
||||||
>
|
>
|
||||||
${AIStarIcon} <span class="label medium">Ask AI</span>
|
${AIStarIcon} <span class="label medium">Ask AI</span>
|
||||||
|
|||||||
20
tests/affine-cloud-copilot/README.md
Normal file
20
tests/affine-cloud-copilot/README.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# AFFiNE Cloud Copilot E2E Tests
|
||||||
|
|
||||||
|
This directory contains end-to-end tests for the AFFiNE Cloud Copilot feature. The tests are organized in a structured way to ensure comprehensive coverage of different functionalities.
|
||||||
|
|
||||||
|
## Test Structure
|
||||||
|
|
||||||
|
The e2e tests are organized into the following categories:
|
||||||
|
|
||||||
|
1. **Basic Tests (`/basic`)**: Tests for verifying core AI capabilities including feature onboarding, authorization workflows, and basic chat interactions.
|
||||||
|
2. **Chat Interaction Tests (`/chat-with`)**: Tests for verifying the AI's interaction with various object types, such as attachments, images, text content, Edgeless elements, etc.
|
||||||
|
3. **AI Action Tests (`/ai-action`)**: Tests for verifying the AI's actions, such as text translation, gramma correction, etc.
|
||||||
|
4. **Insertion Tests (`/insertion`)**: Tests for verifying answer insertion functionality.
|
||||||
|
|
||||||
|
## Test Utilities
|
||||||
|
|
||||||
|
The `/utils` directory contains shared utilities for testing:
|
||||||
|
|
||||||
|
- **ChatPanelUtils**: Helper functions for chat panel interactions
|
||||||
|
- **EditorUtils**: Helper functions for editor operations
|
||||||
|
- **TestUtils**: General test utilities and setup functions
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/BrainstormIdeasWithMindMap', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate a mind map for the selected content', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { brainstormMindMap } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'Panda'
|
||||||
|
);
|
||||||
|
const { answer, responses } = await brainstormMindMap();
|
||||||
|
await expect(answer.locator('mini-mindmap-preview')).toBeVisible();
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate a mind map for the selected text block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { brainstormMindMap } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(page, 'Panda');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await brainstormMindMap();
|
||||||
|
await expect(answer.locator('mini-mindmap-preview')).toBeVisible();
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate a mind map for the selected note block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { brainstormMindMap } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(page, 'Panda');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await brainstormMindMap();
|
||||||
|
await expect(answer.locator('mini-mindmap-preview')).toBeVisible();
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
});
|
||||||
87
tests/affine-cloud-copilot/e2e/ai-action/change-tone.spec.ts
Normal file
87
tests/affine-cloud-copilot/e2e/ai-action/change-tone.spec.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/ChangeTone', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support changing the tone of the selected content', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { changeTone } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a great note-taking app'
|
||||||
|
);
|
||||||
|
const { answer, responses } = await changeTone('informal');
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below', 'replace-selection']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support changing the tone of the selected text block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { changeTone } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a great note-taking app'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { answer, responses } = await changeTone('informal');
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support changing the tone of the selected note block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { changeTone } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a great note-taking app'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { answer, responses } = await changeTone('informal');
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { changeTone } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a great note-taking app'
|
||||||
|
);
|
||||||
|
const { answer } = await changeTone('informal');
|
||||||
|
const replace = answer.getByTestId('answer-replace');
|
||||||
|
await replace.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'action',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const {
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(panelAnswer).toHaveText(/AFFiNE/);
|
||||||
|
await expect(prompt).toHaveText(/Change tone/);
|
||||||
|
await expect(actionName).toHaveText(/Change tone/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/CheckCodeError', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support check code error', async ({ page, utils }) => {
|
||||||
|
const { checkCodeError } = await utils.editor.askAIWithCode(
|
||||||
|
page,
|
||||||
|
'consloe.log("Hello,World!");',
|
||||||
|
'javascript'
|
||||||
|
);
|
||||||
|
const { answer, responses } = await checkCodeError();
|
||||||
|
await expect(answer).toHaveText(/console/);
|
||||||
|
await expect(responses).toEqual(
|
||||||
|
new Set(['insert-below', 'replace-selection'])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { checkCodeError } = await utils.editor.askAIWithCode(
|
||||||
|
page,
|
||||||
|
'consloe.log("Hello,World!");',
|
||||||
|
'javascript'
|
||||||
|
);
|
||||||
|
const { answer } = await checkCodeError();
|
||||||
|
const insert = answer.getByTestId('answer-insert-below');
|
||||||
|
await insert.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [{ role: 'action' }]);
|
||||||
|
const {
|
||||||
|
message,
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(
|
||||||
|
message.getByTestId('original-text').locator('affine-code')
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(panelAnswer).toHaveText(/console/);
|
||||||
|
await expect(prompt).toHaveText(/Check the code error of the follow code/);
|
||||||
|
await expect(actionName).toHaveText(/Check code error/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/ContinueWithAI', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support continue in chat panel', async ({ page, utils }) => {
|
||||||
|
const { continueWithAi } = await utils.editor.askAIWithText(page, 'Apple');
|
||||||
|
await continueWithAi();
|
||||||
|
const chatPanelInput = await page.getByTestId('chat-panel-input-container');
|
||||||
|
const quote = await chatPanelInput.getByTestId('chat-selection-quote');
|
||||||
|
await expect(quote).toHaveText(/Apple/, { timeout: 10000 });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/ContinueWriting', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support continue writing the selected content', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
await page.setViewportSize({ width: 1280, height: 2000 });
|
||||||
|
const { continueWriting } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer, responses } = await continueWriting();
|
||||||
|
await expect(answer).toHaveText(/,*/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below', 'replace-selection']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support continue writing the selected text block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { continueWriting } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { answer, responses } = await continueWriting();
|
||||||
|
await expect(answer).toHaveText(/,*/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support continue writing the selected note block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { continueWriting } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { answer, responses } = await continueWriting();
|
||||||
|
await expect(answer).toHaveText(/,*/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { continueWriting } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer } = await continueWriting();
|
||||||
|
const insert = answer.getByTestId('answer-insert-below');
|
||||||
|
await insert.click();
|
||||||
|
await utils.chatPanel.waitForHistory(
|
||||||
|
page,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
role: 'action',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
10000
|
||||||
|
);
|
||||||
|
const {
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(panelAnswer).toHaveText(/,*/);
|
||||||
|
await expect(prompt).toHaveText(/Continue the following text/);
|
||||||
|
await expect(actionName).toHaveText(/Continue writing/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('expand mindmap node', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should expand the mindmap node', async ({ page, utils }) => {
|
||||||
|
let id: string;
|
||||||
|
const { expandMindMapNode } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
id = await utils.editor.createMindmap(page);
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
// Select the first child in the mindmap
|
||||||
|
const { id: childId } = await utils.editor.getMindMapNode(
|
||||||
|
page,
|
||||||
|
id!,
|
||||||
|
[0, 0]
|
||||||
|
);
|
||||||
|
await utils.editor.selectElementInEdgeless(page, [childId]);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await expandMindMapNode();
|
||||||
|
// Child node should be expanded
|
||||||
|
await expect(async () => {
|
||||||
|
const newChild = await utils.editor.getMindMapNode(page, id!, [0, 0, 0]);
|
||||||
|
expect(newChild).toBeDefined();
|
||||||
|
}).toPass({ timeout: 20000 });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/ExplainCode', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support explain code', async ({ page, utils }) => {
|
||||||
|
const { explainCode } = await utils.editor.askAIWithCode(
|
||||||
|
page,
|
||||||
|
'console.log("Hello, World!");',
|
||||||
|
'javascript'
|
||||||
|
);
|
||||||
|
const { answer } = await explainCode();
|
||||||
|
await expect(answer).toHaveText(/console.log/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { explainCode } = await utils.editor.askAIWithCode(
|
||||||
|
page,
|
||||||
|
'console.log("Hello, World!");',
|
||||||
|
'javascript'
|
||||||
|
);
|
||||||
|
const { answer } = await explainCode();
|
||||||
|
const insert = answer.getByTestId('answer-insert-below');
|
||||||
|
await insert.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [{ role: 'action' }]);
|
||||||
|
const {
|
||||||
|
message,
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(
|
||||||
|
message.getByTestId('original-text').locator('affine-code')
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(panelAnswer).toHaveText(/console.log/);
|
||||||
|
await expect(prompt).toHaveText(/Analyze and explain the follow code/);
|
||||||
|
await expect(actionName).toHaveText(/Explain this code/);
|
||||||
|
});
|
||||||
|
});
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,78 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/ExplainSelection', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support explaining the selected content', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { explainSelection } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'LLM(AI)'
|
||||||
|
);
|
||||||
|
const { answer, responses } = await explainSelection();
|
||||||
|
await expect(answer).toHaveText(/Large Language Model/, { timeout: 20000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below', 'replace-selection']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support explaining the selected text block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { explainSelection } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(page, 'LLM(AI)');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { answer, responses } = await explainSelection();
|
||||||
|
await expect(answer).toHaveText(/Large Language Model/, { timeout: 20000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support explaining the selected note block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { explainSelection } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(page, 'LLM(AI)');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { answer, responses } = await explainSelection();
|
||||||
|
await expect(answer).toHaveText(/Large Language Model/, { timeout: 20000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { explainSelection } = await utils.editor.askAIWithText(page, 'LLM');
|
||||||
|
const { answer } = await explainSelection();
|
||||||
|
const replace = answer.getByTestId('answer-replace');
|
||||||
|
await replace.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'action',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const {
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(panelAnswer).toHaveText(/Large Language Model/);
|
||||||
|
await expect(prompt).toHaveText(/Analyze and explain the follow text/);
|
||||||
|
await expect(actionName).toHaveText(/Explain this/);
|
||||||
|
});
|
||||||
|
});
|
||||||
143
tests/affine-cloud-copilot/e2e/ai-action/find-actions.spec.ts
Normal file
143
tests/affine-cloud-copilot/e2e/ai-action/find-actions.spec.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/FindActions', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should find actions for selected content', async ({ page, utils }) => {
|
||||||
|
const { findActions } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
`Choose a Booking Platform
|
||||||
|
Enter Travel Details
|
||||||
|
Compare and Select Flights`
|
||||||
|
);
|
||||||
|
const { answer, responses } = await findActions();
|
||||||
|
const todos = await answer.locator('affine-list').all();
|
||||||
|
|
||||||
|
const expectedTexts = [
|
||||||
|
'Choose a Booking Platform',
|
||||||
|
'Enter Travel Details',
|
||||||
|
'Compare and Select Flights',
|
||||||
|
];
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
todos.map(async (todo, index) => {
|
||||||
|
await expect(
|
||||||
|
todo.locator('.affine-list-block__todo-prefix')
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(todo).toHaveText(expectedTexts[index]);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(responses).toEqual(new Set(['insert-below', 'replace-selection']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should find actions for selected text block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { findActions } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(
|
||||||
|
page,
|
||||||
|
'Choose a Booking Platform'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { answer, responses } = await findActions();
|
||||||
|
const todos = await answer.locator('affine-list').all();
|
||||||
|
const expectedTexts = [
|
||||||
|
'Choose a Booking Platform',
|
||||||
|
'Enter Travel Details',
|
||||||
|
'Compare and Select Flights',
|
||||||
|
];
|
||||||
|
await Promise.all(
|
||||||
|
todos.map(async (todo, index) => {
|
||||||
|
await expect(
|
||||||
|
todo.locator('.affine-list-block__todo-prefix')
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(todo).toHaveText(expectedTexts[index]);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should find actions for selected note block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { findActions } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(
|
||||||
|
page,
|
||||||
|
'Choose a Booking Platform'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { answer, responses } = await findActions();
|
||||||
|
const todos = await answer.locator('affine-list').all();
|
||||||
|
const expectedTexts = [
|
||||||
|
'Choose a Booking Platform',
|
||||||
|
'Enter Travel Details',
|
||||||
|
'Compare and Select Flights',
|
||||||
|
];
|
||||||
|
await Promise.all(
|
||||||
|
todos.map(async (todo, index) => {
|
||||||
|
await expect(
|
||||||
|
todo.locator('.affine-list-block__todo-prefix')
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(todo).toHaveText(expectedTexts[index]);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { findActions } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
`Choose a Booking Platform
|
||||||
|
Enter Travel Details
|
||||||
|
Compare and Select Flights`
|
||||||
|
);
|
||||||
|
const { answer } = await findActions();
|
||||||
|
const replace = answer.getByTestId('answer-replace');
|
||||||
|
await replace.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'action',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const {
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
const todos = await panelAnswer.locator('affine-list').all();
|
||||||
|
|
||||||
|
const expectedTexts = [
|
||||||
|
'Choose a Booking Platform',
|
||||||
|
'Enter Travel Details',
|
||||||
|
'Compare and Select Flights',
|
||||||
|
];
|
||||||
|
await Promise.all(
|
||||||
|
todos.map(async (todo, index) => {
|
||||||
|
await expect(
|
||||||
|
todo.locator('.affine-list-block__todo-prefix')
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(todo).toHaveText(expectedTexts[index]);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await expect(prompt).toHaveText(/Find action items of the follow text/);
|
||||||
|
await expect(actionName).toHaveText(/Find action items from it/);
|
||||||
|
});
|
||||||
|
});
|
||||||
84
tests/affine-cloud-copilot/e2e/ai-action/fix-grammar.spec.ts
Normal file
84
tests/affine-cloud-copilot/e2e/ai-action/fix-grammar.spec.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/FixGrammar', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support fixing grammatical errors in the selected content', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { fixGrammar } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'I is a student'
|
||||||
|
);
|
||||||
|
const { answer, responses } = await fixGrammar();
|
||||||
|
await expect(answer).toHaveText(/I am a student/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below', 'replace-selection']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support fixing grammatical errors in the selected text block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { fixGrammar } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(page, 'I is a student');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { answer, responses } = await fixGrammar();
|
||||||
|
await expect(answer).toHaveText(/I am a student/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support fixing grammatical errors in the selected note block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { fixGrammar } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(page, 'I is a student');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { answer, responses } = await fixGrammar();
|
||||||
|
await expect(answer).toHaveText(/I am a student/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { fixGrammar } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'I is a student'
|
||||||
|
);
|
||||||
|
const { answer } = await fixGrammar();
|
||||||
|
await expect(answer).toHaveText(/I am a student/, { timeout: 10000 });
|
||||||
|
const replace = answer.getByTestId('answer-replace');
|
||||||
|
await replace.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'action',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const {
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(panelAnswer).toHaveText(/I am a student/);
|
||||||
|
await expect(prompt).toHaveText(
|
||||||
|
/Improve the grammar of the following text/
|
||||||
|
);
|
||||||
|
await expect(actionName).toHaveText(/Improve grammar for it/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/FixSpelling', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support fixing spelling errors in the selected content', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { fixSpelling } = await utils.editor.askAIWithText(page, 'Appel');
|
||||||
|
const { answer, responses } = await fixSpelling();
|
||||||
|
await expect(answer).toHaveText(/Apple/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below', 'replace-selection']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support fixing spelling errors in the selected text block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { fixSpelling } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(page, 'Appel');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { answer, responses } = await fixSpelling();
|
||||||
|
await expect(answer).toHaveText(/Apple/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support fixing spelling errors in the selected note block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { fixSpelling } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(page, 'Appel');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { answer, responses } = await fixSpelling();
|
||||||
|
await expect(answer).toHaveText(/Apple/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { fixSpelling } = await utils.editor.askAIWithText(page, 'Appel');
|
||||||
|
const { answer } = await fixSpelling();
|
||||||
|
await expect(answer).toHaveText(/Apple/, { timeout: 10000 });
|
||||||
|
const replace = answer.getByTestId('answer-replace');
|
||||||
|
await replace.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'action',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const {
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(panelAnswer).toHaveText(/Apple/);
|
||||||
|
await expect(prompt).toHaveText(
|
||||||
|
/Correct the spelling of the following text/
|
||||||
|
);
|
||||||
|
await expect(actionName).toHaveText(/Fix spelling for it/);
|
||||||
|
});
|
||||||
|
});
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,71 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/GenerateAnImageWithText', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate an image for the selected content', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { generateImage } = await utils.editor.askAIWithText(page, 'Panda');
|
||||||
|
const { answer, responses } = await generateImage();
|
||||||
|
await expect(answer.getByTestId('ai-answer-image')).toBeVisible();
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate an image for the selected text block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { generateImage } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(page, 'Panda');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await generateImage();
|
||||||
|
await expect(answer.getByTestId('ai-answer-image')).toBeVisible();
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate an image for the selected note block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { generateImage } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(page, 'Panda');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await generateImage();
|
||||||
|
await expect(answer.getByTestId('ai-answer-image')).toBeVisible();
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { generateImage } = await utils.editor.askAIWithText(page, 'Panda');
|
||||||
|
const { answer } = await generateImage();
|
||||||
|
const insert = answer.getByTestId('answer-insert-below');
|
||||||
|
await insert.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'action',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const { answer: panelAnswer, actionName } =
|
||||||
|
await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(
|
||||||
|
panelAnswer.getByTestId('generated-image').locator('img')
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(actionName).toHaveText(/image/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/GenerateHeadings', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate headings for selected content', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { generateHeadings } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer, responses } = await generateHeadings();
|
||||||
|
await Promise.race([
|
||||||
|
answer.locator('h1').isVisible(),
|
||||||
|
answer.locator('h2').isVisible(),
|
||||||
|
answer.locator('h3').isVisible(),
|
||||||
|
]);
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-above', 'replace-selection']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test.fixme(
|
||||||
|
'should generate headings for selected text block in edgeless',
|
||||||
|
async ({ page, utils }) => {
|
||||||
|
const { generateHeadings } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { answer, responses } = await generateHeadings();
|
||||||
|
await Promise.race([
|
||||||
|
answer.locator('h1').isVisible(),
|
||||||
|
answer.locator('h2').isVisible(),
|
||||||
|
answer.locator('h3').isVisible(),
|
||||||
|
]);
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-above']));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test.fixme(
|
||||||
|
'should generate headings for selected note block in edgeless',
|
||||||
|
async ({ page, utils }) => {
|
||||||
|
const { generateHeadings } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { answer, responses } = await generateHeadings();
|
||||||
|
await Promise.race([
|
||||||
|
answer.locator('h1').isVisible(),
|
||||||
|
answer.locator('h2').isVisible(),
|
||||||
|
answer.locator('h3').isVisible(),
|
||||||
|
]);
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-above']));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { generateHeadings } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer } = await generateHeadings();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
const replace = answer.getByTestId('answer-replace');
|
||||||
|
await replace.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'action',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const {
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(panelAnswer).toHaveText(/AFFiNE/);
|
||||||
|
await Promise.race([
|
||||||
|
panelAnswer.locator('h1').isVisible(),
|
||||||
|
panelAnswer.locator('h2').isVisible(),
|
||||||
|
panelAnswer.locator('h3').isVisible(),
|
||||||
|
]);
|
||||||
|
await expect(prompt).toHaveText(/Create headings of the follow text/);
|
||||||
|
await expect(actionName).toHaveText(/Create headings/);
|
||||||
|
});
|
||||||
|
});
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,86 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/GenerateOutline', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate outline for selected content', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { generateOutline } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer, responses } = await generateOutline();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below', 'replace-selection']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate outline for selected text block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { generateOutline } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await generateOutline();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate outline for selected note block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { generateOutline } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await generateOutline();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { generateOutline } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer } = await generateOutline();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
const replace = answer.getByTestId('answer-replace');
|
||||||
|
await replace.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'action',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const {
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(panelAnswer).toHaveText(/AFFiNE/);
|
||||||
|
await expect(prompt).toHaveText(/Write an outline about this/);
|
||||||
|
await expect(actionName).toHaveText(/Write outline/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/GeneratePresentation', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate a presentation for the selected content', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { generatePresentation } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer, responses } = await generatePresentation();
|
||||||
|
await expect(answer.locator('ai-slides-renderer')).toBeVisible();
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate a presentation for the selected text block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { generatePresentation } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await generatePresentation();
|
||||||
|
await expect(answer.locator('ai-slides-renderer')).toBeVisible();
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate a presentation for the selected note block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { generatePresentation } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await generatePresentation();
|
||||||
|
await expect(answer.locator('ai-slides-renderer')).toBeVisible();
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
});
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,80 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/ImproveWriting', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support improving the writing of the selected content', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { improveWriting } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is so smart'
|
||||||
|
);
|
||||||
|
const { answer, responses } = await improveWriting();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below', 'replace-selection']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support improving the writing of the selected text block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { improveWriting } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(page, 'AFFiNE is so smart');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await improveWriting();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support improving the writing of the selected note block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { improveWriting } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(page, 'AFFiNE is so smart');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await improveWriting();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { improveWriting } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is so smart'
|
||||||
|
);
|
||||||
|
const { answer } = await improveWriting();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
const replace = answer.getByTestId('answer-replace');
|
||||||
|
await replace.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'action',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const {
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(panelAnswer).toHaveText(/AFFiNE/);
|
||||||
|
await expect(prompt).toHaveText(/Improve the follow text/);
|
||||||
|
await expect(actionName).toHaveText(/Improve writing for it/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/MakeItLonger', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support making the selected content longer', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { makeItLonger } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer, responses } = await makeItLonger();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below', 'replace-selection']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support making the selected text block longer in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { makeItLonger } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await makeItLonger();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support making the selected note block longer in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { makeItLonger } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await makeItLonger();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { makeItLonger } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer } = await makeItLonger();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
const replace = answer.getByTestId('answer-replace');
|
||||||
|
await replace.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'action',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const {
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(panelAnswer).toHaveText(/AFFiNE/);
|
||||||
|
await expect(prompt).toHaveText(/Expand the following text/);
|
||||||
|
await expect(actionName).toHaveText(/Make it longer/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/MakeItReal', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support making the selected content to real', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { makeItReal } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer, responses } = await makeItReal();
|
||||||
|
await expect(answer.locator('iframe')).toBeVisible({ timeout: 30000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support making the selected text block to real in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { makeItReal } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await makeItReal();
|
||||||
|
await expect(answer.locator('iframe')).toBeVisible({ timeout: 30000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support making the selected note block to real in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { makeItReal } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await makeItReal();
|
||||||
|
await expect(answer.locator('iframe')).toBeVisible({ timeout: 30000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { makeItReal } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer } = await makeItReal();
|
||||||
|
const insert = answer.getByTestId('answer-insert-below');
|
||||||
|
await insert.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'action',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const {
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(panelAnswer.locator('affine-code')).toBeVisible();
|
||||||
|
await expect(prompt).toHaveText(/Write a web page of follow text/);
|
||||||
|
await expect(actionName).toHaveText(/Make it real with text/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/MakeItShorter', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support making the selected content shorter', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { makeItShorter } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer, responses } = await makeItShorter();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below', 'replace-selection']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support making the selected text block shorter in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { makeItShorter } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await makeItShorter();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support making the selected note block shorter in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { makeItShorter } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await makeItShorter();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { makeItShorter } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer } = await makeItShorter();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
const replace = answer.getByTestId('answer-replace');
|
||||||
|
await replace.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'action',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const {
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(panelAnswer).toHaveText(/AFFiNE/);
|
||||||
|
await expect(prompt).toHaveText(/Shorten the follow text/);
|
||||||
|
await expect(actionName).toHaveText(/Make it shorter/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/RegenerateMindMap', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support regenerate the mind map for mindmap root', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
let id: string;
|
||||||
|
const { regenerateMindMap } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
id = await utils.editor.createMindmap(page);
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
const { id: rootId } = await utils.editor.getMindMapNode(page, id!, [
|
||||||
|
0,
|
||||||
|
]);
|
||||||
|
await utils.editor.selectElementInEdgeless(page, [rootId]);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { answer, responses } = await regenerateMindMap();
|
||||||
|
await expect(answer.locator('mini-mindmap-preview')).toBeVisible();
|
||||||
|
expect(responses).toEqual(new Set(['replace-selection']));
|
||||||
|
});
|
||||||
|
});
|
||||||
86
tests/affine-cloud-copilot/e2e/ai-action/summarize.spec.ts
Normal file
86
tests/affine-cloud-copilot/e2e/ai-action/summarize.spec.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/Summarize', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support summarizing the selected content', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { summarize } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer, responses } = await summarize();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below', 'replace-selection']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support summarizing the selected text block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { summarize } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await summarize();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support summarizing the selected note block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { summarize } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await summarize();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { summarize } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer } = await summarize();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
const replace = answer.getByTestId('answer-replace');
|
||||||
|
await replace.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'action',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const {
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(panelAnswer).toHaveText(/AFFiNE/);
|
||||||
|
await expect(prompt).toHaveText(/Summary the follow text/);
|
||||||
|
await expect(actionName).toHaveText(/Summary/);
|
||||||
|
});
|
||||||
|
});
|
||||||
74
tests/affine-cloud-copilot/e2e/ai-action/translate.spec.ts
Normal file
74
tests/affine-cloud-copilot/e2e/ai-action/translate.spec.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/Translate', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support translating the selected content', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { translate } = await utils.editor.askAIWithText(page, 'Apple');
|
||||||
|
const { answer, responses } = await translate('German');
|
||||||
|
await expect(answer).toHaveText(/Apfel/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below', 'replace-selection']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support translating the selected text block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { translate } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(page, 'Apple');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await translate('German');
|
||||||
|
await expect(answer).toHaveText(/Apfel/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support translating the selected note block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { translate } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(page, 'Apple');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await translate('German');
|
||||||
|
await expect(answer).toHaveText(/Apfel/, { timeout: 10000 });
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('support show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { translate } = await utils.editor.askAIWithText(page, 'Apple');
|
||||||
|
const { answer } = await translate('German');
|
||||||
|
await expect(answer).toHaveText(/Apfel/, { timeout: 10000 });
|
||||||
|
const replace = answer.getByTestId('answer-replace');
|
||||||
|
await replace.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'action',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const {
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(panelAnswer).toHaveText(/Apfel/);
|
||||||
|
await expect(prompt).toHaveText(/Translate/);
|
||||||
|
await expect(actionName).toHaveText(/Translate/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/WriteAnArticleAboutThis', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate an article for the selected content', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { writeArticle } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer, responses } = await writeArticle();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/);
|
||||||
|
expect(responses).toEqual(new Set(['insert-below', 'replace-selection']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support writing an article for the selected text block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { writeArticle } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await writeArticle();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/);
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support writing an article for the selected note block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { writeArticle } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await writeArticle();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/);
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { writeArticle } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer } = await writeArticle();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/);
|
||||||
|
const replace = answer.getByTestId('answer-replace');
|
||||||
|
await replace.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'action',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const {
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(panelAnswer).toHaveText(/AFFiNE/);
|
||||||
|
await expect(prompt).toHaveText(/Write an article about this/);
|
||||||
|
await expect(actionName).toHaveText(/Write an article about this/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/WriteAnBlogPostAboutThis', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate an blog post for the selected content', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { writeBlogPost } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer, responses } = await writeBlogPost();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/);
|
||||||
|
expect(responses).toEqual(new Set(['insert-below', 'replace-selection']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support writing an blog post for the selected text block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { writeBlogPost } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await writeBlogPost();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/);
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support writing an blog post for the selected note block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { writeBlogPost } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await writeBlogPost();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/);
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { writeBlogPost } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer } = await writeBlogPost();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/);
|
||||||
|
const replace = answer.getByTestId('answer-replace');
|
||||||
|
await replace.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'action',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const {
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(panelAnswer).toHaveText(/AFFiNE/);
|
||||||
|
await expect(prompt).toHaveText(/Write a blog post about this/);
|
||||||
|
await expect(actionName).toHaveText(/Write a blog post about this/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/WriteAnPoemAboutThis', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate an poem for the selected content', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { writePoem } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer, responses } = await writePoem();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/);
|
||||||
|
expect(responses).toEqual(new Set(['insert-below', 'replace-selection']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate an poem for the selected text block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { writePoem } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await writePoem();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/);
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate an poem for the selected note block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { writePoem } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await writePoem();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/);
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { writePoem } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer } = await writePoem();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/);
|
||||||
|
const replace = answer.getByTestId('answer-replace');
|
||||||
|
await replace.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'action',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const {
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(panelAnswer).toHaveText(/AFFiNE/);
|
||||||
|
await expect(prompt).toHaveText(/Write a poem about this/);
|
||||||
|
await expect(actionName).toHaveText(/Write a poem about this/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIAction/WriteAnTweetAboutThis', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate an tweet for the selected content', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { writeTwitterPost } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer, responses } = await writeTwitterPost();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/);
|
||||||
|
expect(responses).toEqual(new Set(['insert-below', 'replace-selection']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate an tweet for the selected text block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { writeTwitterPost } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await writeTwitterPost();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/);
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate an tweet for the selected note block in edgeless', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { writeTwitterPost } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer, responses } = await writeTwitterPost();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/);
|
||||||
|
expect(responses).toEqual(new Set(['insert-below']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show chat history in chat panel', async ({ page, utils }) => {
|
||||||
|
const { writeTwitterPost } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE is a workspace with fully merged docs'
|
||||||
|
);
|
||||||
|
const { answer } = await writeTwitterPost();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/);
|
||||||
|
const replace = answer.getByTestId('answer-replace');
|
||||||
|
await replace.click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'action',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const {
|
||||||
|
answer: panelAnswer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
} = await utils.chatPanel.getLatestAIActionMessage(page);
|
||||||
|
await expect(panelAnswer).toHaveText(/AFFiNE/);
|
||||||
|
await expect(prompt).toHaveText(/Write a twitter about this/);
|
||||||
|
await expect(actionName).toHaveText(/Write a twitter about this/);
|
||||||
|
});
|
||||||
|
});
|
||||||
27
tests/affine-cloud-copilot/e2e/base/base-test.ts
Normal file
27
tests/affine-cloud-copilot/e2e/base/base-test.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// eslint-disable no-empty-pattern
|
||||||
|
import { test as base } from '@affine-test/kit/playwright';
|
||||||
|
|
||||||
|
import { ChatPanelUtils } from '../utils/chat-panel-utils';
|
||||||
|
import { EditorUtils } from '../utils/editor-utils';
|
||||||
|
import { TestUtils } from '../utils/test-utils';
|
||||||
|
|
||||||
|
interface TestUtilsFixtures {
|
||||||
|
utils: {
|
||||||
|
testUtils: TestUtils;
|
||||||
|
chatPanel: typeof ChatPanelUtils;
|
||||||
|
editor: typeof EditorUtils;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const test = base.extend<TestUtilsFixtures>({
|
||||||
|
utils: async ({}, use) => {
|
||||||
|
const testUtils = TestUtils.getInstance();
|
||||||
|
await use({
|
||||||
|
testUtils,
|
||||||
|
chatPanel: ChatPanelUtils,
|
||||||
|
editor: EditorUtils,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TestFixtures = typeof test;
|
||||||
28
tests/affine-cloud-copilot/e2e/basic/authority.spec.ts
Normal file
28
tests/affine-cloud-copilot/e2e/basic/authority.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIBasic/Authority', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show error & login button when no login', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
await utils.chatPanel.makeChat(page, 'Hello');
|
||||||
|
|
||||||
|
await expect(page.getByTestId('ai-error')).toBeVisible();
|
||||||
|
await expect(page.getByTestId('ai-error-action-button')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support login in error state', async ({ page, utils }) => {
|
||||||
|
await utils.chatPanel.makeChat(page, 'Hello');
|
||||||
|
const loginButton = page.getByTestId('ai-error-action-button');
|
||||||
|
await loginButton.click();
|
||||||
|
|
||||||
|
await expect(page.getByTestId('auth-modal')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
346
tests/affine-cloud-copilot/e2e/basic/chat.spec.ts
Normal file
346
tests/affine-cloud-copilot/e2e/basic/chat.spec.ts
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIBasic/Chat', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should display empty state when no messages', async ({ page }) => {
|
||||||
|
// Verify empty state UI
|
||||||
|
await expect(page.getByTestId('chat-panel-empty-state')).toBeVisible();
|
||||||
|
await expect(page.getByTestId('ai-onboarding')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`should send message and receive AI response:
|
||||||
|
- send message
|
||||||
|
- AI is loading
|
||||||
|
- AI generating
|
||||||
|
- AI success
|
||||||
|
`, async ({ page, utils }) => {
|
||||||
|
// Type and send a message
|
||||||
|
await utils.chatPanel.makeChat(page, 'Introduce AFFiNE to me');
|
||||||
|
|
||||||
|
// AI is loading
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Introduce AFFiNE to me',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'loading',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(page.getByTestId('ai-loading')).toBeVisible();
|
||||||
|
|
||||||
|
// AI Generating
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Introduce AFFiNE to me',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'transmitting',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(page.getByTestId('ai-loading')).not.toBeVisible();
|
||||||
|
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Introduce AFFiNE to me',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support stop generating', async ({ page, utils }) => {
|
||||||
|
await utils.chatPanel.makeChat(page, 'Introduce AFFiNE to me');
|
||||||
|
|
||||||
|
// AI Generating
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Introduce AFFiNE to me',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'transmitting',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await page.getByTestId('chat-panel-stop').click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Introduce AFFiNE to me',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render ai actions inline if the answer is the last one in the list, otherwise, nest them under the "More" menu', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
await utils.chatPanel.makeChat(page, 'Hello, how can you help me?');
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello, how can you help me?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(page.getByTestId('chat-action-list')).toBeVisible();
|
||||||
|
await utils.chatPanel.makeChat(page, 'Nice to meet you');
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello, how can you help me?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'idle',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Nice to meet you',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const firstAnswer = await page
|
||||||
|
.getByTestId('chat-message-assistant')
|
||||||
|
.first();
|
||||||
|
const more = firstAnswer.getByTestId('action-more-button');
|
||||||
|
await more.click();
|
||||||
|
await expect(firstAnswer.getByTestId('chat-actions')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show scroll indicator when there are many messages', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
// Set window height to 100px to ensure scroll indicator appears
|
||||||
|
await page.setViewportSize({ width: 1280, height: 400 });
|
||||||
|
|
||||||
|
// Type and send a message
|
||||||
|
await utils.chatPanel.makeChat(
|
||||||
|
page,
|
||||||
|
'Hello, write a poem about the moon with 50 words.'
|
||||||
|
);
|
||||||
|
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello, write a poem about the moon with 50 words.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Wait for the answer to be completely rendered
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
|
// Scroll up to trigger scroll indicator
|
||||||
|
const chatMessagesContainer = page.getByTestId(
|
||||||
|
'chat-panel-messages-container'
|
||||||
|
);
|
||||||
|
await chatMessagesContainer.evaluate(el => {
|
||||||
|
el.scrollTop = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const scrollDownIndicator = page.getByTestId(
|
||||||
|
'chat-panel-scroll-down-indicator'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify scroll indicator appears
|
||||||
|
await expect(scrollDownIndicator).toBeVisible();
|
||||||
|
|
||||||
|
// Click scroll indicator to scroll to bottom
|
||||||
|
await scrollDownIndicator.click();
|
||||||
|
|
||||||
|
// Verify scroll indicator disappears
|
||||||
|
await expect(scrollDownIndicator).not.toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show error when request failed', async ({ page, utils }) => {
|
||||||
|
// Simulate network error by disconnecting
|
||||||
|
await page.route('**/graphql', route => route.abort('failed'));
|
||||||
|
|
||||||
|
// Send a message that will fail
|
||||||
|
await utils.chatPanel.makeChat(page, 'Hello');
|
||||||
|
|
||||||
|
await expect(page.getByTestId('ai-error')).toBeVisible();
|
||||||
|
await expect(page.getByTestId('action-retry-button')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support retrying failed messages', async ({ page, utils }) => {
|
||||||
|
// Simulate network error by disconnecting
|
||||||
|
await page.route('**/graphql', route => route.abort('failed'));
|
||||||
|
|
||||||
|
// Send a message that will fail
|
||||||
|
await utils.chatPanel.makeChat(page, 'Hello');
|
||||||
|
|
||||||
|
// Verify error state
|
||||||
|
await expect(page.getByTestId('ai-error')).toBeVisible();
|
||||||
|
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'error',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Reconnect network
|
||||||
|
await page.route('**/graphql', route => route.continue());
|
||||||
|
|
||||||
|
await page.getByTestId('action-retry-button').click();
|
||||||
|
|
||||||
|
// Verify message is resent and AI responds
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support retrying question', async ({ page, utils }) => {
|
||||||
|
await utils.chatPanel.makeChat(
|
||||||
|
page,
|
||||||
|
'Introduce Large Language Model in under 500 words'
|
||||||
|
);
|
||||||
|
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Introduce Large Language Model in under 500 words',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { actions } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
await page.pause();
|
||||||
|
await actions.retry();
|
||||||
|
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Introduce Large Language Model in under 500 words',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'transmitting',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Introduce Large Language Model in under 500 words',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support sending message with button', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
await utils.chatPanel.typeChat(page, 'Hello');
|
||||||
|
await page.getByTestId('chat-panel-send').click();
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'loading',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support clearing chat', async ({ page, utils }) => {
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
await utils.chatPanel.makeChat(page, 'Hello');
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
await utils.chatPanel.clearChat(page);
|
||||||
|
await utils.chatPanel.waitForHistory(page, []);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support copying answer', async ({ page, utils }) => {
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
await utils.chatPanel.makeChat(page, 'Hello');
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { actions } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
await actions.copy();
|
||||||
|
await page.getByText('Copied to clipboard').isVisible();
|
||||||
|
await expect(async () => {
|
||||||
|
const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
const clipboardText = await page.evaluate(() =>
|
||||||
|
navigator.clipboard.readText()
|
||||||
|
);
|
||||||
|
expect(clipboardText).toBe(content);
|
||||||
|
}).toPass({ timeout: 5000 });
|
||||||
|
});
|
||||||
|
});
|
||||||
72
tests/affine-cloud-copilot/e2e/basic/onboarding.spec.ts
Normal file
72
tests/affine-cloud-copilot/e2e/basic/onboarding.spec.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe.configure({ mode: 'parallel' });
|
||||||
|
|
||||||
|
test.describe('AIBasic/Onboarding', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show AI onboarding', async ({ page }) => {
|
||||||
|
await expect(page.getByTestId('ai-onboarding')).toBeVisible();
|
||||||
|
|
||||||
|
// Show options
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('read-foreign-language-article-with-ai')
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('tidy-an-article-with-ai-mindmap-action')
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('add-illustrations-to-the-article')
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByTestId('complete-writing-with-ai')).toBeVisible();
|
||||||
|
await expect(page.getByTestId('freely-communicate-with-ai')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('read a foreign language article with AI', async ({ page, utils }) => {
|
||||||
|
await page.getByTestId('read-foreign-language-article-with-ai').click();
|
||||||
|
|
||||||
|
await utils.editor.isEdgelessMode(page);
|
||||||
|
const docTitle = await utils.editor.getDocTitle(page);
|
||||||
|
await expect(docTitle).toContain('Read a foreign language');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('tidy an article with AI MindMap Action', async ({ page, utils }) => {
|
||||||
|
await page.getByTestId('tidy-an-article-with-ai-mindmap-action').click();
|
||||||
|
|
||||||
|
await utils.editor.isEdgelessMode(page);
|
||||||
|
const docTitle = await utils.editor.getDocTitle(page);
|
||||||
|
await expect(docTitle).toContain('Tidy');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('add illustrations to the article', async ({ page, utils }) => {
|
||||||
|
await page.getByTestId('add-illustrations-to-the-article').click();
|
||||||
|
|
||||||
|
await utils.editor.isEdgelessMode(page);
|
||||||
|
const docTitle = await utils.editor.getDocTitle(page);
|
||||||
|
await expect(docTitle).toContain('Add illustrations');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('complete writing with AI', async ({ page, utils }) => {
|
||||||
|
await page.getByTestId('complete-writing-with-ai').click();
|
||||||
|
|
||||||
|
await utils.editor.isEdgelessMode(page);
|
||||||
|
const docTitle = await utils.editor.getDocTitle(page);
|
||||||
|
await expect(docTitle).toContain('Complete writing');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('freely communicate with AI', async ({ page, utils }) => {
|
||||||
|
await page.getByTestId('freely-communicate-with-ai').click();
|
||||||
|
|
||||||
|
await utils.editor.isEdgelessMode(page);
|
||||||
|
const docTitle = await utils.editor.getDocTitle(page);
|
||||||
|
await expect(docTitle).toContain('Freely communicate');
|
||||||
|
});
|
||||||
|
});
|
||||||
89
tests/affine-cloud-copilot/e2e/chat-with/attachments.spec.ts
Normal file
89
tests/affine-cloud-copilot/e2e/chat-with/attachments.spec.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIChatWith/Attachments', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('support chat with attachment', async ({ page, utils }) => {
|
||||||
|
const textContent = 'EEee is a cute cat';
|
||||||
|
const buffer = Buffer.from(textContent);
|
||||||
|
|
||||||
|
await utils.chatPanel.chatWithAttachments(
|
||||||
|
page,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'test.txt',
|
||||||
|
mimeType: 'text/plain',
|
||||||
|
buffer: buffer,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'What is EEee?'
|
||||||
|
);
|
||||||
|
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'What is EEee?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(async () => {
|
||||||
|
const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
expect(content).toMatch(/EEee/);
|
||||||
|
}).toPass({ timeout: 10000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('support chat with multiple attachments', async ({ page, utils }) => {
|
||||||
|
const textContent1 = 'EEee is a cute cat';
|
||||||
|
const textContent2 = 'FFff is a cute dog';
|
||||||
|
const buffer1 = Buffer.from(textContent1);
|
||||||
|
const buffer2 = Buffer.from(textContent2);
|
||||||
|
|
||||||
|
await utils.chatPanel.chatWithAttachments(
|
||||||
|
page,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'document1.txt',
|
||||||
|
mimeType: 'text/plain',
|
||||||
|
buffer: buffer1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'document2.txt',
|
||||||
|
mimeType: 'text/plain',
|
||||||
|
buffer: buffer2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'What is EEee? What is FFff?'
|
||||||
|
);
|
||||||
|
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'What is EEee? What is FFff?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(async () => {
|
||||||
|
const { content, message } =
|
||||||
|
await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
expect(content).toMatch(/EEee/);
|
||||||
|
expect(content).toMatch(/FFff/);
|
||||||
|
expect(await message.locator('affine-footnote-node').count()).toBe(2);
|
||||||
|
}).toPass({ timeout: 20000 });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIChatWith/Collections', () => {});
|
||||||
82
tests/affine-cloud-copilot/e2e/chat-with/doc.spec.ts
Normal file
82
tests/affine-cloud-copilot/e2e/chat-with/doc.spec.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { focusDocTitle } from '@affine-test/kit/utils/editor';
|
||||||
|
import {
|
||||||
|
clickNewPageButton,
|
||||||
|
waitForEditorLoad,
|
||||||
|
} from '@affine-test/kit/utils/page-logic';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIChatWith/Doc', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('support chat with specified doc', async ({ page, utils }) => {
|
||||||
|
// Initialize the doc
|
||||||
|
await focusDocTitle(page);
|
||||||
|
await page.keyboard.insertText('Test Doc');
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
await page.keyboard.insertText('EEee is a cute cat');
|
||||||
|
|
||||||
|
await utils.chatPanel.chatWithDoc(page, 'Test Doc');
|
||||||
|
|
||||||
|
await utils.chatPanel.makeChat(page, 'What is EEee?');
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'What is EEee?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(async () => {
|
||||||
|
const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
expect(content).toMatch(/EEee/);
|
||||||
|
}).toPass({ timeout: 10000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('support chat with specified docs', async ({ page, utils }) => {
|
||||||
|
// Initialize the doc 1
|
||||||
|
await focusDocTitle(page);
|
||||||
|
await page.keyboard.insertText('Test Doc1');
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
await page.keyboard.insertText('EEee is a cute cat');
|
||||||
|
|
||||||
|
// Initialize the doc 2
|
||||||
|
await clickNewPageButton(page);
|
||||||
|
await waitForEditorLoad(page);
|
||||||
|
await focusDocTitle(page);
|
||||||
|
await page.keyboard.insertText('Test Doc2');
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
await page.keyboard.insertText('FFff is a cute dog');
|
||||||
|
|
||||||
|
await utils.chatPanel.chatWithDoc(page, 'Test Doc1');
|
||||||
|
await utils.chatPanel.chatWithDoc(page, 'Test Doc2');
|
||||||
|
|
||||||
|
await utils.chatPanel.makeChat(page, 'What is EEee? What is FFff?');
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'What is EEee? What is FFff?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(async () => {
|
||||||
|
const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
expect(content).toMatch(/EEee/);
|
||||||
|
expect(content).toMatch(/FFff/);
|
||||||
|
}).toPass({ timeout: 10000 });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import type { EdgelessRootBlockComponent } from '@blocksuite/affine/blocks/root';
|
||||||
|
import type { GfxModel } from '@blocksuite/std/gfx';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIChatWith/EdgelessMindMap', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support replace mindmap with the regenerated one', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
let id: string;
|
||||||
|
const { regenerateMindMap } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
id = await utils.editor.createMindmap(page);
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
const { id: rootId } = await utils.editor.getMindMapNode(page, id!, [
|
||||||
|
0,
|
||||||
|
]);
|
||||||
|
await utils.editor.selectElementInEdgeless(page, [rootId]);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { answer } = await regenerateMindMap();
|
||||||
|
await expect(answer.locator('mini-mindmap-preview')).toBeVisible();
|
||||||
|
const replace = answer.getByTestId('answer-replace');
|
||||||
|
await replace.click();
|
||||||
|
|
||||||
|
// Expect original mindmap to be replaced
|
||||||
|
const mindmaps = await page.evaluate(() => {
|
||||||
|
const edgelessBlock = document.querySelector(
|
||||||
|
'affine-edgeless-root'
|
||||||
|
) as EdgelessRootBlockComponent;
|
||||||
|
const mindmaps = edgelessBlock?.gfx.gfxElements
|
||||||
|
.filter((el: GfxModel) => 'type' in el && el.type === 'mindmap')
|
||||||
|
.map((el: GfxModel) => el.id);
|
||||||
|
return mindmaps;
|
||||||
|
});
|
||||||
|
expect(mindmaps).toHaveLength(1);
|
||||||
|
expect(mindmaps?.[0]).not.toBe(id!);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIChatWith/EdgelessNoteBlock', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support insert a new note block below the current', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { translate } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessNote(page, 'Apple');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer } = await translate('German');
|
||||||
|
await expect(answer).toHaveText(/Apfel/, { timeout: 10000 });
|
||||||
|
const insertBelow = answer.getByTestId('answer-insert-below');
|
||||||
|
await insertBelow.click();
|
||||||
|
await expect(page.locator('affine-edgeless-note').nth(1)).toHaveText(
|
||||||
|
/Apfel/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIChatWith/EdgelessShape', () => {});
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIChatWith/EdgelessTextBlock', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support insert answer below the current text', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
const { translate } = await utils.editor.askAIWithEdgeless(
|
||||||
|
page,
|
||||||
|
async () => {
|
||||||
|
await utils.editor.createEdgelessText(page, 'Apple');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { answer } = await translate('German');
|
||||||
|
await expect(answer).toHaveText(/Apfel/, { timeout: 10000 });
|
||||||
|
const insertBelow = answer.getByTestId('answer-insert-below');
|
||||||
|
await insertBelow.click();
|
||||||
|
await expect(page.locator('affine-edgeless-text')).toHaveText(
|
||||||
|
/Apple[\s\S]*Apfel/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
35
tests/affine-cloud-copilot/e2e/chat-with/image-block.spec.ts
Normal file
35
tests/affine-cloud-copilot/e2e/chat-with/image-block.spec.ts
Normal file
File diff suppressed because one or more lines are too long
77
tests/affine-cloud-copilot/e2e/chat-with/image.spec.ts
Normal file
77
tests/affine-cloud-copilot/e2e/chat-with/image.spec.ts
Normal file
File diff suppressed because one or more lines are too long
104
tests/affine-cloud-copilot/e2e/chat-with/network.spec.ts
Normal file
104
tests/affine-cloud-copilot/e2e/chat-with/network.spec.ts
Normal file
File diff suppressed because one or more lines are too long
3
tests/affine-cloud-copilot/e2e/chat-with/tags.spec.ts
Normal file
3
tests/affine-cloud-copilot/e2e/chat-with/tags.spec.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIChatWith/tags', () => {});
|
||||||
130
tests/affine-cloud-copilot/e2e/chat-with/text.spec.ts
Normal file
130
tests/affine-cloud-copilot/e2e/chat-with/text.spec.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIChatWith/Text', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support stop generating', async ({ page, utils }) => {
|
||||||
|
await utils.editor.askAIWithText(page, 'Appel');
|
||||||
|
await page.getByTestId('action-fix-grammar').click();
|
||||||
|
await expect(page.getByTestId('ai-generating')).toBeVisible();
|
||||||
|
const stop = await page.getByTestId('ai-stop');
|
||||||
|
await stop.click();
|
||||||
|
await expect(page.getByTestId('ai-generating')).not.toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support copy answer', async ({ page, utils }) => {
|
||||||
|
const { translate } = await utils.editor.askAIWithText(page, 'Apple');
|
||||||
|
const { answer } = await translate('German');
|
||||||
|
await expect(answer).toHaveText(/Apfel/, { timeout: 10000 });
|
||||||
|
const copy = answer.getByTestId('answer-copy-button');
|
||||||
|
await copy.click();
|
||||||
|
await expect(answer.getByTestId('answer-copied')).toBeVisible();
|
||||||
|
const clipboardText = await page.evaluate(() =>
|
||||||
|
navigator.clipboard.readText()
|
||||||
|
);
|
||||||
|
expect(clipboardText).toBe('Apfel');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support insert below', async ({ page, utils }) => {
|
||||||
|
const { translate } = await utils.editor.askAIWithText(page, 'Apple');
|
||||||
|
const { answer } = await translate('German');
|
||||||
|
await expect(answer).toHaveText(/Apfel/, { timeout: 10000 });
|
||||||
|
const insertBelow = answer.getByTestId('answer-insert-below');
|
||||||
|
await insertBelow.click();
|
||||||
|
const content = await utils.editor.getEditorContent(page);
|
||||||
|
expect(content).toBe('Apple\nApfel');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support insert above', async ({ page, utils }) => {
|
||||||
|
const { generateHeadings } = await utils.editor.askAIWithText(
|
||||||
|
page,
|
||||||
|
'AFFiNE'
|
||||||
|
);
|
||||||
|
const { answer } = await generateHeadings();
|
||||||
|
await answer.locator('h1').isVisible();
|
||||||
|
await expect(answer).toHaveText(/AFFiNE/, { timeout: 10000 });
|
||||||
|
|
||||||
|
const insertAbove = answer.getByTestId('answer-insert-above');
|
||||||
|
await insertAbove.click();
|
||||||
|
const content = await utils.editor.getEditorContent(page);
|
||||||
|
expect(content).toBe('AFFiNE\nAFFiNE');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support replace selection', async ({ page, utils }) => {
|
||||||
|
const { translate } = await utils.editor.askAIWithText(page, 'Apple');
|
||||||
|
const { answer } = await translate('German');
|
||||||
|
await expect(answer).toHaveText(/Apfel/, { timeout: 10000 });
|
||||||
|
const replace = answer.getByTestId('answer-replace');
|
||||||
|
await replace.click();
|
||||||
|
const content = await utils.editor.getEditorContent(page);
|
||||||
|
expect(content).toBe('Apfel');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support continue in chat', async ({ page, utils }) => {
|
||||||
|
const { translate } = await utils.editor.askAIWithText(page, 'Apple');
|
||||||
|
const { answer } = await translate('German');
|
||||||
|
await expect(answer).toHaveText(/Apfel/, { timeout: 10000 });
|
||||||
|
const continueInChat = answer.getByTestId('answer-continue-in-chat');
|
||||||
|
await continueInChat.click();
|
||||||
|
const chatPanelInput = await page.getByTestId('chat-panel-input-container');
|
||||||
|
const quote = await chatPanelInput.getByTestId('chat-selection-quote');
|
||||||
|
await expect(quote).toHaveText(/Apple/, { timeout: 10000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support regenerate', async ({ page, utils }) => {
|
||||||
|
const { translate } = await utils.editor.askAIWithText(page, 'Apple');
|
||||||
|
const { answer } = await translate('German');
|
||||||
|
const regenerate = answer.getByTestId('answer-regenerate');
|
||||||
|
await regenerate.click();
|
||||||
|
const content = await utils.editor.getEditorContent(page);
|
||||||
|
expect(content).toBe('Apple');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show error when request failed', async ({ page, utils }) => {
|
||||||
|
await page.route('**/graphql', route => route.abort('failed'));
|
||||||
|
await utils.editor.askAIWithText(page, 'Appel');
|
||||||
|
await page.getByTestId('action-fix-spelling').click();
|
||||||
|
await expect(page.getByTestId('ai-error')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support retry when error', async ({ page, utils }) => {
|
||||||
|
await page.route('**/graphql', route => route.abort('failed'));
|
||||||
|
await utils.editor.askAIWithText(page, 'Appel');
|
||||||
|
await page.getByTestId('action-fix-spelling').click();
|
||||||
|
const aiPanelContainer = await page.getByTestId('ai-panel-container');
|
||||||
|
|
||||||
|
await page.route('**/graphql', route => route.continue());
|
||||||
|
await aiPanelContainer.getByTestId('error-retry').click();
|
||||||
|
const answer = await utils.editor.waitForAiAnswer(page);
|
||||||
|
await expect(answer).toHaveText(/Apple/, { timeout: 10000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support discard', async ({ page, utils }) => {
|
||||||
|
const { translate } = await utils.editor.askAIWithText(page, 'Apple');
|
||||||
|
const { answer } = await translate('German');
|
||||||
|
const discard = answer.getByTestId('answer-discard');
|
||||||
|
await discard.click();
|
||||||
|
await expect(answer).not.toBeVisible();
|
||||||
|
const content = await utils.editor.getEditorContent(page);
|
||||||
|
expect(content).toBe('Apple');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support discard when click outside', async ({ page, utils }) => {
|
||||||
|
const { translate } = await utils.editor.askAIWithText(page, 'Apple');
|
||||||
|
const { answer } = await translate('German');
|
||||||
|
await page.mouse.click(0, 0);
|
||||||
|
await expect(page.getByText('Discard the AI result')).toBeVisible();
|
||||||
|
await page.getByTestId('confirm-modal-confirm').click();
|
||||||
|
await expect(answer).not.toBeVisible();
|
||||||
|
const content = await utils.editor.getEditorContent(page);
|
||||||
|
expect(content).toBe('Apple');
|
||||||
|
});
|
||||||
|
});
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIInsertion/AddToEdgelessAsNote', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should only show option in edgeless mode', async ({ page, utils }) => {
|
||||||
|
await utils.editor.focusToEditor(page);
|
||||||
|
await utils.chatPanel.makeChat(page, 'Hello');
|
||||||
|
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('action-add-to-edgeless-as-note')
|
||||||
|
).not.toBeVisible();
|
||||||
|
|
||||||
|
await utils.editor.switchToEdgelessMode(page);
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('action-add-to-edgeless-as-note')
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should add to edgeless as note in edgeless mode', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
await utils.editor.switchToEdgelessMode(page);
|
||||||
|
|
||||||
|
// Delete default note
|
||||||
|
await (await page.waitForSelector('affine-edgeless-note')).click();
|
||||||
|
page.keyboard.press('Delete');
|
||||||
|
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
await utils.chatPanel.makeChat(page, 'Hello');
|
||||||
|
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { actions } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
await actions.addAsNote();
|
||||||
|
await page.getByText('New note created');
|
||||||
|
|
||||||
|
await expect(async () => {
|
||||||
|
const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
const noteContent = await utils.editor.getNoteContent(page);
|
||||||
|
expect(noteContent).toBe(content);
|
||||||
|
}).toPass({ timeout: 5000 });
|
||||||
|
});
|
||||||
|
});
|
||||||
201
tests/affine-cloud-copilot/e2e/insertion/insert.spec.ts
Normal file
201
tests/affine-cloud-copilot/e2e/insertion/insert.spec.ts
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { focusDocTitle } from '@affine-test/kit/utils/editor';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIInsertion/Insert', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should insert content below selected block in page mode', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
// Create tow blocks
|
||||||
|
// - Hello Block
|
||||||
|
// - World Block
|
||||||
|
await utils.editor.focusToEditor(page);
|
||||||
|
await page.keyboard.insertText('Hello Block');
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
await page.keyboard.insertText('World Block');
|
||||||
|
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
await utils.chatPanel.makeChat(page, 'Hello');
|
||||||
|
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Focus to Hello
|
||||||
|
// - Hello<cursor />
|
||||||
|
await page.getByText('Hello Block').click();
|
||||||
|
|
||||||
|
const { actions } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
await actions.insert();
|
||||||
|
|
||||||
|
await expect(async () => {
|
||||||
|
const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
const editorContent = await utils.editor.getEditorContent(page);
|
||||||
|
expect(editorContent).toBe(`Hello Block\n${content}\nWorld Block`);
|
||||||
|
}).toPass({ timeout: 5000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should insert content below selected block in edgeless mode', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
await utils.editor.switchToEdgelessMode(page);
|
||||||
|
await utils.editor.focusToEditor(page);
|
||||||
|
await page.keyboard.insertText('Hello Block');
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
await page.keyboard.insertText('World Block');
|
||||||
|
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
await utils.chatPanel.makeChat(page, 'Hello');
|
||||||
|
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Focus to Hello
|
||||||
|
// - Hello<cursor />
|
||||||
|
await page.locator('affine-edgeless-note').dblclick();
|
||||||
|
await page.getByText('Hello Block').click();
|
||||||
|
|
||||||
|
const { actions } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
await actions.insert();
|
||||||
|
|
||||||
|
await expect(async () => {
|
||||||
|
const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
const noteContent = await utils.editor.getNoteContent(page);
|
||||||
|
expect(noteContent).toBe(`Hello Block\n${content}\nWorld Block`);
|
||||||
|
}).toPass({ timeout: 5000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should insert content at the end of the page when no block is selected', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
// Create tow blocks
|
||||||
|
// - Hello Block
|
||||||
|
// - World Block
|
||||||
|
await utils.editor.focusToEditor(page);
|
||||||
|
await page.keyboard.insertText('Hello Block');
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
await page.keyboard.insertText('World Block');
|
||||||
|
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
await utils.chatPanel.makeChat(page, 'Hello');
|
||||||
|
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Focus Doc Title
|
||||||
|
// - Hello<cursor />
|
||||||
|
await focusDocTitle(page);
|
||||||
|
|
||||||
|
const { actions } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
await actions.insert();
|
||||||
|
|
||||||
|
await expect(async () => {
|
||||||
|
const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
const editorContent = await utils.editor.getEditorContent(page);
|
||||||
|
expect(editorContent).toBe(`Hello Block\nWorld Block\n${content}`);
|
||||||
|
}).toPass({ timeout: 5000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should insert content at the end of the note when no block is selected in edgeless mode', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
await utils.editor.switchToEdgelessMode(page);
|
||||||
|
await utils.editor.focusToEditor(page);
|
||||||
|
await page.keyboard.insertText('Hello Block');
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
await page.keyboard.insertText('World Block');
|
||||||
|
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
await utils.chatPanel.makeChat(page, 'Hello');
|
||||||
|
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { actions } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
await actions.insert();
|
||||||
|
|
||||||
|
await expect(async () => {
|
||||||
|
const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
const noteContent = await utils.editor.getNoteContent(page);
|
||||||
|
expect(noteContent).toBe(`Hello Block\nWorld Block\n${content}`);
|
||||||
|
}).toPass({ timeout: 5000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should create a new note when no block or note is selected in edgeless mode', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
await utils.editor.switchToEdgelessMode(page);
|
||||||
|
|
||||||
|
// Delete default note
|
||||||
|
await (await page.waitForSelector('affine-edgeless-note')).click();
|
||||||
|
page.keyboard.press('Delete');
|
||||||
|
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
await utils.chatPanel.makeChat(page, 'Hello');
|
||||||
|
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { actions } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
await actions.insert();
|
||||||
|
|
||||||
|
await expect(async () => {
|
||||||
|
const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
const noteContent = await utils.editor.getNoteContent(page);
|
||||||
|
expect(noteContent).toBe(content);
|
||||||
|
}).toPass({ timeout: 5000 });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIInsertion/SaveAsBlock', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should save content as a chat block in page mode', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
await utils.chatPanel.makeChat(page, 'Hello');
|
||||||
|
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { actions } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
await actions.saveAsBlock();
|
||||||
|
|
||||||
|
// Switch to edgeless mode
|
||||||
|
await utils.editor.isEdgelessMode(page);
|
||||||
|
|
||||||
|
// Verify the ai block is created
|
||||||
|
await page.waitForSelector('affine-edgeless-ai-chat');
|
||||||
|
const aiBlock = await page.locator('affine-edgeless-ai-chat');
|
||||||
|
await expect(aiBlock).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should save content as a chat block in edgeless mode', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
await utils.editor.switchToEdgelessMode(page);
|
||||||
|
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
await utils.chatPanel.makeChat(page, 'Hello');
|
||||||
|
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { actions } = await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
await actions.saveAsBlock();
|
||||||
|
await page.getByText('Successfully saved chat to a block');
|
||||||
|
|
||||||
|
// Verify the ai block is created
|
||||||
|
await page.waitForSelector('affine-edgeless-ai-chat');
|
||||||
|
const aiBlock = await page.locator('affine-edgeless-ai-chat');
|
||||||
|
await expect(aiBlock).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
75
tests/affine-cloud-copilot/e2e/insertion/save-as-doc.spec.ts
Normal file
75
tests/affine-cloud-copilot/e2e/insertion/save-as-doc.spec.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { loginUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import { test } from '../base/base-test';
|
||||||
|
|
||||||
|
test.describe('AIInsertion/SaveAsDoc', () => {
|
||||||
|
test.beforeEach(async ({ page, utils }) => {
|
||||||
|
const user = await utils.testUtils.getUser();
|
||||||
|
await loginUser(page, user);
|
||||||
|
await utils.testUtils.setupTestEnvironment(page);
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should save content as a doc in page mode', async ({ page, utils }) => {
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
await utils.chatPanel.makeChat(page, 'Hello');
|
||||||
|
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Wait for the assistant answer to be completely rendered
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
const { actions, content } =
|
||||||
|
await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
await actions.saveAsDoc();
|
||||||
|
await page.getByText('New doc created');
|
||||||
|
|
||||||
|
// Verify the ai block is created
|
||||||
|
const editorContent = await utils.editor.getEditorContent(page);
|
||||||
|
expect(editorContent).toBe(content);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should save content as a doc in edgeless mode', async ({
|
||||||
|
page,
|
||||||
|
utils,
|
||||||
|
}) => {
|
||||||
|
await utils.editor.switchToEdgelessMode(page);
|
||||||
|
|
||||||
|
await utils.chatPanel.openChatPanel(page);
|
||||||
|
await utils.chatPanel.makeChat(page, 'Hello');
|
||||||
|
|
||||||
|
await utils.chatPanel.waitForHistory(page, [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
status: 'success',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Wait for the assistant answer to be completely rendered
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
const { actions, content } =
|
||||||
|
await utils.chatPanel.getLatestAssistantMessage(page);
|
||||||
|
await actions.saveAsDoc();
|
||||||
|
await page.getByText('New doc created');
|
||||||
|
|
||||||
|
// Switch to page mode
|
||||||
|
await utils.editor.isPageMode(page);
|
||||||
|
|
||||||
|
// Verify the ai block is created
|
||||||
|
const editorContent = await utils.editor.getEditorContent(page);
|
||||||
|
expect(editorContent).toBe(content);
|
||||||
|
});
|
||||||
|
});
|
||||||
3
tests/affine-cloud-copilot/e2e/types/global.d.ts
vendored
Normal file
3
tests/affine-cloud-copilot/e2e/types/global.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
interface Window {
|
||||||
|
showOpenFilePicker?: () => Promise<FileSystemFileHandle[]>;
|
||||||
|
}
|
||||||
276
tests/affine-cloud-copilot/e2e/utils/chat-panel-utils.ts
Normal file
276
tests/affine-cloud-copilot/e2e/utils/chat-panel-utils.ts
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
// eslint-disable eslint-plugin-unicorn(prefer-dom-node-dataset
|
||||||
|
import type { Page } from '@playwright/test';
|
||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
type ChatStatus = 'loading' | 'success' | 'error' | 'idle' | 'transmitting';
|
||||||
|
|
||||||
|
type ChatUserMessage = {
|
||||||
|
role: 'user';
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ChatAssistantMessage = {
|
||||||
|
role: 'assistant';
|
||||||
|
status: ChatStatus;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ChatActionMessage = {
|
||||||
|
role: 'action';
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ChatMessage = ChatUserMessage | ChatAssistantMessage | ChatActionMessage;
|
||||||
|
|
||||||
|
export class ChatPanelUtils {
|
||||||
|
public static async openChatPanel(page: Page) {
|
||||||
|
if (await page.getByTestId('sidebar-tab-chat').isHidden()) {
|
||||||
|
await page.getByTestId('right-sidebar-toggle').click({
|
||||||
|
delay: 200,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await page.getByTestId('sidebar-tab-chat').click();
|
||||||
|
await expect(page.getByTestId('sidebar-tab-content-chat')).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async typeChat(page: Page, content: string) {
|
||||||
|
await page.getByTestId('chat-panel-input').focus();
|
||||||
|
await page.keyboard.type(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async typeChatSequentially(page: Page, content: string) {
|
||||||
|
const input = await page.locator('chat-panel-input textarea').nth(0);
|
||||||
|
await input.pressSequentially(content, {
|
||||||
|
delay: 50,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async makeChat(page: Page, content: string) {
|
||||||
|
await this.openChatPanel(page);
|
||||||
|
await this.typeChat(page, content);
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async clearChat(page: Page) {
|
||||||
|
await page.getByTestId('chat-panel-clear').click();
|
||||||
|
await page.getByTestId('confirm-modal-confirm').click();
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async collectHistory(page: Page) {
|
||||||
|
return await page.evaluate(() => {
|
||||||
|
const chatPanel = document.querySelector<HTMLElement>(
|
||||||
|
'[data-testid="chat-panel-messages"]'
|
||||||
|
);
|
||||||
|
if (!chatPanel) {
|
||||||
|
return [] as ChatMessage[];
|
||||||
|
}
|
||||||
|
const messages = chatPanel.querySelectorAll<HTMLElement>(
|
||||||
|
'chat-message-user,chat-message-assistant,chat-message-action'
|
||||||
|
);
|
||||||
|
|
||||||
|
return Array.from(messages).map(m => {
|
||||||
|
const isAssistant = m.dataset.testid === 'chat-message-assistant';
|
||||||
|
const isChatAction = m.dataset.testid === 'chat-message-action';
|
||||||
|
|
||||||
|
const isUser = !isAssistant && !isChatAction;
|
||||||
|
|
||||||
|
if (isUser) {
|
||||||
|
return {
|
||||||
|
role: 'user' as const,
|
||||||
|
content:
|
||||||
|
m.querySelector<HTMLElement>(
|
||||||
|
'[data-testid="chat-content-pure-text"]'
|
||||||
|
)?.innerText || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAssistant) {
|
||||||
|
return {
|
||||||
|
role: 'assistant' as const,
|
||||||
|
status: m.dataset.status as ChatStatus,
|
||||||
|
title: m.querySelector<HTMLElement>('.user-info')?.innerText || '',
|
||||||
|
content:
|
||||||
|
m.querySelector<HTMLElement>('chat-content-rich-text editor-host')
|
||||||
|
?.innerText || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be chat action at this point
|
||||||
|
return {
|
||||||
|
role: 'action' as const,
|
||||||
|
title: m.querySelector<HTMLElement>('.user-info')?.innerText || '',
|
||||||
|
content:
|
||||||
|
m.querySelector<HTMLElement>('chat-content-rich-text editor-host')
|
||||||
|
?.innerText || '',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static expectHistory(
|
||||||
|
history: ChatMessage[],
|
||||||
|
expected: (
|
||||||
|
| Partial<ChatUserMessage>
|
||||||
|
| Partial<ChatAssistantMessage>
|
||||||
|
| Partial<ChatActionMessage>
|
||||||
|
)[]
|
||||||
|
) {
|
||||||
|
expect(history).toHaveLength(expected.length);
|
||||||
|
history.forEach((message, index) => {
|
||||||
|
const expectedMessage = expected[index];
|
||||||
|
expect(message).toMatchObject(expectedMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async expectToHaveHistory(
|
||||||
|
page: Page,
|
||||||
|
expected: (
|
||||||
|
| Partial<ChatUserMessage>
|
||||||
|
| Partial<ChatAssistantMessage>
|
||||||
|
| Partial<ChatActionMessage>
|
||||||
|
)[]
|
||||||
|
) {
|
||||||
|
const history = await this.collectHistory(page);
|
||||||
|
this.expectHistory(history, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async waitForHistory(
|
||||||
|
page: Page,
|
||||||
|
expected: (
|
||||||
|
| Partial<ChatUserMessage>
|
||||||
|
| Partial<ChatAssistantMessage>
|
||||||
|
| Partial<ChatActionMessage>
|
||||||
|
)[],
|
||||||
|
timeout = 2 * 60000
|
||||||
|
) {
|
||||||
|
await expect(async () => {
|
||||||
|
const history = await this.collectHistory(page);
|
||||||
|
this.expectHistory(history, expected);
|
||||||
|
}).toPass({ timeout });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getLatestAssistantMessage(page: Page) {
|
||||||
|
const message = page.getByTestId('chat-message-assistant').last();
|
||||||
|
const actions = await message.getByTestId('chat-actions');
|
||||||
|
const actionList = await message.getByTestId('chat-action-list');
|
||||||
|
return {
|
||||||
|
message,
|
||||||
|
content: await message
|
||||||
|
.locator('chat-content-rich-text editor-host')
|
||||||
|
.innerText(),
|
||||||
|
actions: {
|
||||||
|
copy: async () => actions.getByTestId('action-copy-button').click(),
|
||||||
|
retry: async () => actions.getByTestId('action-retry-button').click(),
|
||||||
|
insert: async () => actionList.getByTestId('action-insert').click(),
|
||||||
|
saveAsBlock: async () =>
|
||||||
|
actionList.getByTestId('action-save-as-block').click(),
|
||||||
|
saveAsDoc: async () =>
|
||||||
|
actionList.getByTestId('action-save-as-doc').click(),
|
||||||
|
addAsNote: async () =>
|
||||||
|
actionList.getByTestId('action-add-to-edgeless-as-note').click(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getLatestAIActionMessage(page: Page) {
|
||||||
|
const message = page.getByTestId('chat-message-action').last();
|
||||||
|
const actionName = await message.getByTestId('action-name');
|
||||||
|
await actionName.click();
|
||||||
|
const answer = await message.getByTestId('answer-prompt');
|
||||||
|
const prompt = await message.getByTestId('chat-message-action-prompt');
|
||||||
|
return {
|
||||||
|
message,
|
||||||
|
answer,
|
||||||
|
prompt,
|
||||||
|
actionName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async chatWithDoc(page: Page, docName: string) {
|
||||||
|
const withButton = await page.getByTestId('chat-panel-with-button');
|
||||||
|
await withButton.click();
|
||||||
|
const withMenu = await page.getByTestId('ai-add-popover');
|
||||||
|
await withMenu.getByText(docName).click();
|
||||||
|
await page.getByTestId('chat-panel-chips').getByText(docName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async chatWithAttachments(
|
||||||
|
page: Page,
|
||||||
|
attachments: { name: string; mimeType: string; buffer: Buffer }[],
|
||||||
|
text: string
|
||||||
|
) {
|
||||||
|
await page.evaluate(() => {
|
||||||
|
delete window.showOpenFilePicker;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const attachment of attachments) {
|
||||||
|
const fileChooserPromise = page.waitForEvent('filechooser');
|
||||||
|
const withButton = await page.getByTestId('chat-panel-with-button');
|
||||||
|
await withButton.click();
|
||||||
|
const withMenu = await page.getByTestId('ai-add-popover');
|
||||||
|
await withMenu.getByTestId('ai-chat-with-files').click();
|
||||||
|
const fileChooser = await fileChooserPromise;
|
||||||
|
await fileChooser.setFiles(attachment);
|
||||||
|
}
|
||||||
|
await expect(async () => {
|
||||||
|
const states = await page
|
||||||
|
.getByTestId('chat-panel-chip')
|
||||||
|
.evaluateAll(elements =>
|
||||||
|
elements.map(el => el.getAttribute('data-state'))
|
||||||
|
);
|
||||||
|
await expect(states).toHaveLength(attachments.length);
|
||||||
|
await expect(states.every(state => state === 'finished')).toBe(true);
|
||||||
|
}).toPass({ timeout: 20000 });
|
||||||
|
|
||||||
|
await page.pause();
|
||||||
|
await this.makeChat(page, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async chatWithImages(
|
||||||
|
page: Page,
|
||||||
|
images: { name: string; mimeType: string; buffer: Buffer }[],
|
||||||
|
text: string
|
||||||
|
) {
|
||||||
|
await page.evaluate(() => {
|
||||||
|
delete window.showOpenFilePicker;
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileChooserPromise = page.waitForEvent('filechooser');
|
||||||
|
// Open file upload dialog
|
||||||
|
await page.getByTestId('chat-panel-input-image-upload').click();
|
||||||
|
|
||||||
|
const fileChooser = await fileChooserPromise;
|
||||||
|
await fileChooser.setFiles(images);
|
||||||
|
|
||||||
|
await page.waitForSelector('chat-panel-input img');
|
||||||
|
await this.makeChat(page, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async enableNetworkSearch(page: Page) {
|
||||||
|
const networkSearch = await page.getByTestId('chat-network-search');
|
||||||
|
if ((await networkSearch.getAttribute('data-active')) === 'false') {
|
||||||
|
await networkSearch.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async disableNetworkSearch(page: Page) {
|
||||||
|
const networkSearch = await page.getByTestId('chat-network-search');
|
||||||
|
if ((await networkSearch.getAttribute('data-active')) === 'true') {
|
||||||
|
await networkSearch.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async isNetworkSearchEnabled(page: Page) {
|
||||||
|
const networkSearch = await page.getByTestId('chat-network-search');
|
||||||
|
return (await networkSearch.getAttribute('aria-disabled')) === 'false';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async isImageUploadEnabled(page: Page) {
|
||||||
|
const imageUpload = await page.getByTestId('chat-panel-input-image-upload');
|
||||||
|
const disabled = await imageUpload.getAttribute('data-disabled');
|
||||||
|
return disabled === 'false';
|
||||||
|
}
|
||||||
|
}
|
||||||
528
tests/affine-cloud-copilot/e2e/utils/editor-utils.ts
Normal file
528
tests/affine-cloud-copilot/e2e/utils/editor-utils.ts
Normal file
@@ -0,0 +1,528 @@
|
|||||||
|
import {
|
||||||
|
createEdgelessNoteBlock,
|
||||||
|
setEdgelessTool,
|
||||||
|
} from '@affine-test/kit/utils/editor';
|
||||||
|
import {
|
||||||
|
pressEscape,
|
||||||
|
selectAllByKeyboard,
|
||||||
|
} from '@affine-test/kit/utils/keyboard';
|
||||||
|
import { getBlockSuiteEditorTitle } from '@affine-test/kit/utils/page-logic';
|
||||||
|
import type { EdgelessRootBlockComponent } from '@blocksuite/affine/blocks/root';
|
||||||
|
import type {
|
||||||
|
MindmapElementModel,
|
||||||
|
ShapeElementModel,
|
||||||
|
} from '@blocksuite/affine-model';
|
||||||
|
import type { GfxModel } from '@blocksuite/std/gfx';
|
||||||
|
import { type Page } from '@playwright/test';
|
||||||
|
export class EditorUtils {
|
||||||
|
public static async focusToEditor(page: Page) {
|
||||||
|
const title = getBlockSuiteEditorTitle(page);
|
||||||
|
await title.focus();
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getEditorContent(page: Page) {
|
||||||
|
let content = '';
|
||||||
|
let retry = 3;
|
||||||
|
while (!content && retry > 0) {
|
||||||
|
const lines = await page.$$('page-editor .inline-editor');
|
||||||
|
const contents = await Promise.all(lines.map(el => el.innerText()));
|
||||||
|
content = contents
|
||||||
|
.map(c => c.replace(/\u200B/g, '').trim())
|
||||||
|
.filter(c => !!c)
|
||||||
|
.join('\n');
|
||||||
|
if (!content) {
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
retry -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getNoteContent(page: Page) {
|
||||||
|
const edgelessNode = await page.waitForSelector(
|
||||||
|
'affine-edgeless-note .edgeless-note-page-content'
|
||||||
|
);
|
||||||
|
return (await edgelessNode.innerText()).replace(/\u200B/g, '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async switchToEdgelessMode(page: Page) {
|
||||||
|
const editor = await page.waitForSelector('page-editor');
|
||||||
|
await page.getByTestId('switch-edgeless-mode-button').click();
|
||||||
|
editor.waitForElementState('hidden');
|
||||||
|
await page.waitForSelector('edgeless-editor');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async switchToPageMode(page: Page) {
|
||||||
|
await page.getByTestId('switch-page-mode-button').click();
|
||||||
|
await page.waitForSelector('page-editor');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async isPageMode(page: Page) {
|
||||||
|
return await page.waitForSelector('page-editor');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async isEdgelessMode(page: Page) {
|
||||||
|
return await page.waitForSelector('edgeless-editor');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getDocTitle(page: Page) {
|
||||||
|
return page.getByTestId('title-edit-button').innerText();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async waitForAiAnswer(page: Page) {
|
||||||
|
const answer = await page.getByTestId('ai-penel-answer');
|
||||||
|
await answer.waitFor({
|
||||||
|
state: 'visible',
|
||||||
|
timeout: 2 * 60000,
|
||||||
|
});
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static createAction(page: Page, action: () => Promise<void>) {
|
||||||
|
return async () => {
|
||||||
|
await action();
|
||||||
|
const responses = new Set<string>();
|
||||||
|
const answer = await this.waitForAiAnswer(page);
|
||||||
|
const responsesMenu = answer.getByTestId('answer-responses');
|
||||||
|
await responsesMenu.isVisible();
|
||||||
|
await responsesMenu.scrollIntoViewIfNeeded({ timeout: 60000 });
|
||||||
|
|
||||||
|
if (await responsesMenu.getByTestId('answer-insert-below').isVisible()) {
|
||||||
|
responses.add('insert-below');
|
||||||
|
}
|
||||||
|
if (await responsesMenu.getByTestId('answer-insert-above').isVisible()) {
|
||||||
|
responses.add('insert-above');
|
||||||
|
}
|
||||||
|
if (await responsesMenu.getByTestId('answer-replace').isVisible()) {
|
||||||
|
responses.add('replace-selection');
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
await responsesMenu.getByTestId('answer-use-as-caption').isVisible()
|
||||||
|
) {
|
||||||
|
responses.add('use-as-caption');
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
await responsesMenu.getByTestId('answer-create-new-note').isVisible()
|
||||||
|
) {
|
||||||
|
responses.add('create-new-note');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
answer: await this.waitForAiAnswer(page),
|
||||||
|
responses,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async createEdgelessText(page: Page, text: string) {
|
||||||
|
await setEdgelessTool(page, 'text');
|
||||||
|
await page.mouse.click(400, 400);
|
||||||
|
await page.locator('affine-edgeless-text').waitFor({ state: 'visible' });
|
||||||
|
await page.waitForTimeout(100);
|
||||||
|
const texts = text.split('\n');
|
||||||
|
for (const [index, line] of texts.entries()) {
|
||||||
|
await page.keyboard.insertText(line);
|
||||||
|
if (index !== texts.length - 1) {
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async createEdgelessNote(page: Page, text: string) {
|
||||||
|
await createEdgelessNoteBlock(page, [500, 300]);
|
||||||
|
const texts = text.split('\n');
|
||||||
|
for (const [index, line] of texts.entries()) {
|
||||||
|
await page.keyboard.insertText(line);
|
||||||
|
if (index !== texts.length - 1) {
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async createMindmap(page: Page) {
|
||||||
|
await page.keyboard.press('m');
|
||||||
|
await page.mouse.click(400, 400);
|
||||||
|
const id = await page.evaluate(() => {
|
||||||
|
const edgelessBlock = document.querySelector(
|
||||||
|
'affine-edgeless-root'
|
||||||
|
) as EdgelessRootBlockComponent;
|
||||||
|
if (!edgelessBlock) {
|
||||||
|
throw new Error('edgeless block not found');
|
||||||
|
}
|
||||||
|
const mindmaps = edgelessBlock.gfx.gfxElements.filter(
|
||||||
|
(el: GfxModel) => 'type' in el && el.type === 'mindmap'
|
||||||
|
);
|
||||||
|
|
||||||
|
return mindmaps[mindmaps.length - 1].id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getMindMapNode(
|
||||||
|
page: Page,
|
||||||
|
mindmapId: string,
|
||||||
|
path: number[]
|
||||||
|
) {
|
||||||
|
return page.evaluate(
|
||||||
|
({ mindmapId, path }) => {
|
||||||
|
const edgelessBlock = document.querySelector(
|
||||||
|
'affine-edgeless-root'
|
||||||
|
) as EdgelessRootBlockComponent;
|
||||||
|
if (!edgelessBlock) {
|
||||||
|
throw new Error('edgeless block not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const mindmap = edgelessBlock.gfx.getElementById(
|
||||||
|
mindmapId
|
||||||
|
) as MindmapElementModel;
|
||||||
|
if (!mindmap) {
|
||||||
|
throw new Error(`Mindmap not found: ${mindmapId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = mindmap.getNodeByPath(path);
|
||||||
|
if (!node) {
|
||||||
|
throw new Error(`Mindmap node not found at: ${path}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = edgelessBlock.gfx.viewport.toViewBound(
|
||||||
|
node.element.elementBound
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: mindmap.getPath(node),
|
||||||
|
id: node.id,
|
||||||
|
text: (node.element as ShapeElementModel).text?.toString() ?? '',
|
||||||
|
rect: {
|
||||||
|
x: rect.x,
|
||||||
|
y: rect.y,
|
||||||
|
w: rect.w,
|
||||||
|
h: rect.h,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mindmapId,
|
||||||
|
path,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async selectElementInEdgeless(page: Page, elements: string[]) {
|
||||||
|
await page.evaluate(
|
||||||
|
({ elements }) => {
|
||||||
|
const edgelessBlock = document.querySelector(
|
||||||
|
'affine-edgeless-root'
|
||||||
|
) as EdgelessRootBlockComponent;
|
||||||
|
if (!edgelessBlock) {
|
||||||
|
throw new Error('edgeless block not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
edgelessBlock.gfx.selection.set({
|
||||||
|
elements,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ elements }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async askAIWithEdgeless(
|
||||||
|
page: Page,
|
||||||
|
createBlock: () => Promise<void>,
|
||||||
|
afterSelected?: () => Promise<void>
|
||||||
|
) {
|
||||||
|
await this.switchToEdgelessMode(page);
|
||||||
|
await selectAllByKeyboard(page);
|
||||||
|
await page.keyboard.press('Delete');
|
||||||
|
await createBlock();
|
||||||
|
await pressEscape(page, 5);
|
||||||
|
await selectAllByKeyboard(page);
|
||||||
|
await afterSelected?.();
|
||||||
|
await page.getByTestId('ask-ai-button').click();
|
||||||
|
|
||||||
|
return {
|
||||||
|
aiImageFilter: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-ai-image-filter').click()
|
||||||
|
),
|
||||||
|
brainstorm: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-brainstorm').click()
|
||||||
|
),
|
||||||
|
brainstormMindMap: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-brainstorm-mindmap').click()
|
||||||
|
),
|
||||||
|
changeTone: (
|
||||||
|
tone: 'professional' | 'informal' | 'friendly' | 'critical' | 'humorous'
|
||||||
|
) =>
|
||||||
|
this.createAction(page, async () => {
|
||||||
|
await page.getByTestId('action-change-tone').hover();
|
||||||
|
await page.getByTestId(`action-change-tone-${tone}`).click();
|
||||||
|
})(),
|
||||||
|
checkCodeError: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-check-code-error').click()
|
||||||
|
),
|
||||||
|
continueWithAi: async () => {
|
||||||
|
page.getByTestId('action-continue-with-ai').click();
|
||||||
|
},
|
||||||
|
continueWriting: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-continue-writing').click()
|
||||||
|
),
|
||||||
|
createHeadings: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-create-headings').click()
|
||||||
|
),
|
||||||
|
explainSelection: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-explain-selection').click()
|
||||||
|
),
|
||||||
|
findActions: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-find-actions').click()
|
||||||
|
),
|
||||||
|
fixGrammar: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-fix-grammar').click()
|
||||||
|
),
|
||||||
|
fixSpelling: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-fix-spelling').click()
|
||||||
|
),
|
||||||
|
generateCaption: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-generate-caption').click()
|
||||||
|
),
|
||||||
|
generateHeadings: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-generate-headings').click()
|
||||||
|
),
|
||||||
|
generateImage: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-generate-image').click()
|
||||||
|
),
|
||||||
|
generateOutline: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-generate-outline').click()
|
||||||
|
),
|
||||||
|
generatePresentation: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-generate-presentation').click()
|
||||||
|
),
|
||||||
|
imageProcessing: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-image-processing').click()
|
||||||
|
),
|
||||||
|
improveGrammar: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-improve-grammar').click()
|
||||||
|
),
|
||||||
|
improveWriting: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-improve-writing').click()
|
||||||
|
),
|
||||||
|
makeItLonger: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-make-it-longer').click()
|
||||||
|
),
|
||||||
|
makeItReal: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-make-it-real').click()
|
||||||
|
),
|
||||||
|
makeItShorter: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-make-it-shorter').click()
|
||||||
|
),
|
||||||
|
summarize: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-summarize').click()
|
||||||
|
),
|
||||||
|
translate: (language: string) =>
|
||||||
|
this.createAction(page, async () => {
|
||||||
|
await page.getByTestId('action-translate').hover();
|
||||||
|
await page.getByTestId(`action-translate-${language}`).click();
|
||||||
|
})(),
|
||||||
|
writeArticle: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-write-article').click()
|
||||||
|
),
|
||||||
|
writeBlogPost: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-write-blog-post').click()
|
||||||
|
),
|
||||||
|
writePoem: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-write-poem').click()
|
||||||
|
),
|
||||||
|
writeTwitterPost: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-write-twitter-post').click()
|
||||||
|
),
|
||||||
|
regenerateMindMap: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-regenerate-mindmap').click()
|
||||||
|
),
|
||||||
|
expandMindMapNode: async () =>
|
||||||
|
page.getByTestId('action-expand-mindmap-node').click(),
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async askAIWithCode(
|
||||||
|
page: Page,
|
||||||
|
code: string,
|
||||||
|
language: string
|
||||||
|
) {
|
||||||
|
await this.focusToEditor(page);
|
||||||
|
await page.keyboard.insertText(`\`\`\`${language}`);
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
await page.keyboard.insertText(code);
|
||||||
|
await page.locator('affine-code').blur();
|
||||||
|
await page.locator('affine-code').hover();
|
||||||
|
await page.getByTestId('ask-ai-button').click();
|
||||||
|
return {
|
||||||
|
explainCode: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-explain-code').click()
|
||||||
|
),
|
||||||
|
checkCodeError: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-check-code-error').click()
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async askAIWithImage(
|
||||||
|
page: Page,
|
||||||
|
image: { name: string; mimeType: string; buffer: Buffer }
|
||||||
|
) {
|
||||||
|
await page.evaluate(() => {
|
||||||
|
delete window.showOpenFilePicker;
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileChooserPromise = page.waitForEvent('filechooser');
|
||||||
|
|
||||||
|
await this.focusToEditor(page);
|
||||||
|
await page.keyboard.press('/');
|
||||||
|
await page.keyboard.insertText('image');
|
||||||
|
await page.locator('affine-slash-menu').getByTestId('Image').click();
|
||||||
|
|
||||||
|
const fileChooser = await fileChooserPromise;
|
||||||
|
await fileChooser.setFiles(image);
|
||||||
|
|
||||||
|
await page.locator('affine-page-image').click();
|
||||||
|
await page.getByTestId('ask-ai-button').click();
|
||||||
|
|
||||||
|
return {
|
||||||
|
explainImage: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-explain-image').click()
|
||||||
|
),
|
||||||
|
generateImage: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-generate-image').click()
|
||||||
|
),
|
||||||
|
generateCaption: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-generate-caption').click()
|
||||||
|
),
|
||||||
|
imageProcessing: (type: string) =>
|
||||||
|
this.createAction(page, async () => {
|
||||||
|
await page.getByTestId('action-image-processing').hover();
|
||||||
|
await page.getByTestId(`action-image-processing-${type}`).click();
|
||||||
|
})(),
|
||||||
|
imageFilter: (style: string) =>
|
||||||
|
this.createAction(page, async () => {
|
||||||
|
await page.getByTestId('action-ai-image-filter').hover();
|
||||||
|
await page.getByTestId(`action-image-filter-${style}`).click();
|
||||||
|
})(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async askAIWithText(page: Page, text: string) {
|
||||||
|
await this.focusToEditor(page);
|
||||||
|
const texts = text.split('\n');
|
||||||
|
for (const [index, line] of texts.entries()) {
|
||||||
|
await page.keyboard.insertText(line);
|
||||||
|
if (index !== texts.length - 1) {
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await page.keyboard.press('ControlOrMeta+A');
|
||||||
|
await page.keyboard.press('ControlOrMeta+A');
|
||||||
|
await page.keyboard.press('ControlOrMeta+A');
|
||||||
|
|
||||||
|
const askAI = await page.locator('page-editor editor-toolbar ask-ai-icon');
|
||||||
|
await askAI.waitFor({
|
||||||
|
state: 'attached',
|
||||||
|
timeout: 5000,
|
||||||
|
});
|
||||||
|
await askAI.click();
|
||||||
|
|
||||||
|
return {
|
||||||
|
aiImageFilter: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-ai-image-filter').click()
|
||||||
|
),
|
||||||
|
brainstorm: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-brainstorm').click()
|
||||||
|
),
|
||||||
|
brainstormMindMap: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-brainstorm-mindmap').click()
|
||||||
|
),
|
||||||
|
changeTone: (
|
||||||
|
tone: 'professional' | 'informal' | 'friendly' | 'critical' | 'humorous'
|
||||||
|
) =>
|
||||||
|
this.createAction(page, async () => {
|
||||||
|
await page.getByTestId('action-change-tone').hover();
|
||||||
|
await page.getByTestId(`action-change-tone-${tone}`).click();
|
||||||
|
})(),
|
||||||
|
checkCodeError: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-check-code-error').click()
|
||||||
|
),
|
||||||
|
continueWithAi: async () => {
|
||||||
|
page.getByTestId('action-continue-with-ai').click();
|
||||||
|
},
|
||||||
|
continueWriting: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-continue-writing').click()
|
||||||
|
),
|
||||||
|
createHeadings: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-create-headings').click()
|
||||||
|
),
|
||||||
|
explainSelection: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-explain-selection').click()
|
||||||
|
),
|
||||||
|
findActions: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-find-actions').click()
|
||||||
|
),
|
||||||
|
fixGrammar: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-fix-grammar').click()
|
||||||
|
),
|
||||||
|
fixSpelling: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-fix-spelling').click()
|
||||||
|
),
|
||||||
|
generateCaption: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-generate-caption').click()
|
||||||
|
),
|
||||||
|
generateHeadings: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-generate-headings').click()
|
||||||
|
),
|
||||||
|
generateImage: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-generate-image').click()
|
||||||
|
),
|
||||||
|
generateOutline: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-generate-outline').click()
|
||||||
|
),
|
||||||
|
generatePresentation: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-generate-presentation').click()
|
||||||
|
),
|
||||||
|
imageProcessing: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-image-processing').click()
|
||||||
|
),
|
||||||
|
improveGrammar: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-improve-grammar').click()
|
||||||
|
),
|
||||||
|
improveWriting: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-improve-writing').click()
|
||||||
|
),
|
||||||
|
makeItLonger: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-make-it-longer').click()
|
||||||
|
),
|
||||||
|
makeItReal: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-make-it-real').click()
|
||||||
|
),
|
||||||
|
makeItShorter: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-make-it-shorter').click()
|
||||||
|
),
|
||||||
|
summarize: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-summarize').click()
|
||||||
|
),
|
||||||
|
translate: (language: string) =>
|
||||||
|
this.createAction(page, async () => {
|
||||||
|
await page.getByTestId('action-translate').hover();
|
||||||
|
await page.getByTestId(`action-translate-${language}`).click();
|
||||||
|
})(),
|
||||||
|
writeArticle: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-write-article').click()
|
||||||
|
),
|
||||||
|
writeBlogPost: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-write-blog-post').click()
|
||||||
|
),
|
||||||
|
writePoem: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-write-poem').click()
|
||||||
|
),
|
||||||
|
writeTwitterPost: this.createAction(page, () =>
|
||||||
|
page.getByTestId('action-write-twitter-post').click()
|
||||||
|
),
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
}
|
||||||
64
tests/affine-cloud-copilot/e2e/utils/test-utils.ts
Normal file
64
tests/affine-cloud-copilot/e2e/utils/test-utils.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { createRandomAIUser } from '@affine-test/kit/utils/cloud';
|
||||||
|
import { openHomePage, setCoreUrl } from '@affine-test/kit/utils/load-page';
|
||||||
|
import {
|
||||||
|
clickNewPageButton,
|
||||||
|
waitForEditorLoad,
|
||||||
|
} from '@affine-test/kit/utils/page-logic';
|
||||||
|
import { createLocalWorkspace } from '@affine-test/kit/utils/workspace';
|
||||||
|
import type { Store } from '@blocksuite/affine/store';
|
||||||
|
import type { Page } from '@playwright/test';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
doc: Store;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TestUtils {
|
||||||
|
private static instance: TestUtils;
|
||||||
|
private isProduction: boolean;
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
this.isProduction = process.env.NODE_ENV === 'production';
|
||||||
|
if (
|
||||||
|
process.env.PLAYWRIGHT_USER_AGENT &&
|
||||||
|
process.env.PLAYWRIGHT_EMAIL &&
|
||||||
|
!process.env.PLAYWRIGHT_PASSWORD
|
||||||
|
) {
|
||||||
|
setCoreUrl(process.env.PLAYWRIGHT_CORE_URL || 'http://localhost:8080');
|
||||||
|
this.isProduction = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance(): TestUtils {
|
||||||
|
if (!TestUtils.instance) {
|
||||||
|
TestUtils.instance = new TestUtils();
|
||||||
|
}
|
||||||
|
return TestUtils.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getUser() {
|
||||||
|
if (
|
||||||
|
!this.isProduction ||
|
||||||
|
!process.env.PLAYWRIGHT_EMAIL ||
|
||||||
|
!process.env.PLAYWRIGHT_PASSWORD
|
||||||
|
) {
|
||||||
|
return createRandomAIUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
email: process.env.PLAYWRIGHT_EMAIL,
|
||||||
|
password: process.env.PLAYWRIGHT_PASSWORD,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setupTestEnvironment(page: Page) {
|
||||||
|
await openHomePage(page);
|
||||||
|
await clickNewPageButton(page);
|
||||||
|
await waitForEditorLoad(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createTestWorkspace(page: Page, name: string = 'test') {
|
||||||
|
await createLocalWorkspace({ name }, page);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user