fix(test): e2e stability (#14749)

#### PR Dependency Tree


* **PR #14749** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved link preview reliability by updating request identification
to better match modern browsers.
* **Tests**
* Made end-to-end and integration tests deterministic and more robust,
improving AI chat, image generation, attachment handling, settings
visibility, and editor flows.
* **Chores**
* Updated underlying tooling versions to enhance stability and
compatibility.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-03-29 23:26:15 +08:00
committed by GitHub
parent 9ca520a81f
commit 91ad783973
12 changed files with 136 additions and 114 deletions
@@ -53,34 +53,34 @@ test.describe('AIChatWith/Attachments', () => {
loggedInPage: page,
utils,
}) => {
const randomStr1 = Math.random().toString(36).substring(2, 6);
const randomStr2 = Math.random().toString(36).substring(2, 6);
const textContent1 = `Attachment${randomStr1} is a cute cat`;
const textContent2 = `Attachment${randomStr2} is a cute dog`;
const textContent1 = 'This attachment describes a cute cat.';
const textContent2 = 'This attachment describes a cute dog.';
const buffer1 = Buffer.from(textContent1);
const buffer2 = Buffer.from(textContent2);
const firstName = 'cat-document.txt';
const secondName = 'dog-document.txt';
await utils.chatPanel.chatWithAttachments(
page,
[
{
name: 'document1.txt',
name: firstName,
mimeType: 'text/plain',
buffer: buffer1,
},
{
name: 'document2.txt',
name: secondName,
mimeType: 'text/plain',
buffer: buffer2,
},
],
`Which animal is Attachment${randomStr1} and which animal is Attachment${randomStr2}? Answer with both attachment names.`
`Which animal is described in ${firstName} and which animal is described in ${secondName}? Answer with both attachment names.`
);
await utils.chatPanel.waitForHistory(page, [
{
role: 'user',
content: `Which animal is Attachment${randomStr1} and which animal is Attachment${randomStr2}? Answer with both attachment names.`,
content: `Which animal is described in ${firstName} and which animal is described in ${secondName}? Answer with both attachment names.`,
},
{
role: 'assistant',
@@ -90,8 +90,8 @@ test.describe('AIChatWith/Attachments', () => {
await expect(async () => {
const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
expect(content).toMatch(new RegExp(`Attachment${randomStr1}`));
expect(content).toMatch(new RegExp(`Attachment${randomStr2}`));
expect(content).toContain(firstName);
expect(content).toContain(secondName);
expect(content).toMatch(/cat/i);
expect(content).toMatch(/dog/i);
}).toPass({ timeout: 20000 });
@@ -37,11 +37,11 @@ test.describe('AIChatWith/Image', () => {
},
]);
const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
await expect(
content.toLowerCase().includes('kitten') ||
content.toLowerCase().includes('cat')
).toBe(true);
await expect(async () => {
const { content } = await utils.chatPanel.getLatestAssistantMessage(page);
expect(content.length).toBeGreaterThan(20);
expect(content).toMatch(/cat|kitten|feline|tabby|fluffy/i);
}).toPass({ timeout: 20000 });
});
test('support chat with mutiple images', async ({
@@ -28,11 +28,10 @@ test.describe('AIInsertion/SaveAsBlock', () => {
const { actions } = await utils.chatPanel.getLatestAssistantMessage(page);
await actions.saveAsBlock();
await page.getByText('Successfully saved chat to a block').waitFor();
// 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();
@@ -513,9 +513,9 @@ test.describe('AISettings/Embedding', () => {
await utils.settings.openSettingsPanel(page);
await page.context().setOffline(true);
await utils.settings.ignoreDocForEmbedding(page, 'Test Doc', false);
await expect(
page.getByText(/Failed to update ignored docs/i)
).toBeVisible();
await page
.getByText(/Failed to update ignored docs/i)
.waitFor({ state: 'visible', timeout: 20000 });
await page.context().setOffline(false);
});
});
@@ -575,7 +575,14 @@ export class EditorUtils {
),
generateImage: this.createAction(page, async () => {
await page.getByTestId('action-generate-image').click();
await page.keyboard.type('generate an image');
const input = page.locator(
'affine-ai-panel-widget .ai-panel-container textarea'
);
await input.waitFor({ state: 'visible' });
await input.fill('generate an image');
await page.getByTestId('ai-panel-input-send').waitFor({
state: 'visible',
});
await page.getByTestId('ai-panel-input-send').click();
}),
generateCaption: this.createAction(page, () =>
@@ -6,6 +6,9 @@ const WORKSPACE_EMBEDDING_SWITCH_TEST_ID = 'workspace-embedding-setting-switch';
export class SettingsPanelUtils {
public static async openSettingsPanel(page: Page) {
if (await page.getByTestId('workspace-setting:embedding').isHidden()) {
await page
.getByTestId('slider-bar-workspace-setting-button')
.waitFor({ state: 'visible' });
await page.getByTestId('slider-bar-workspace-setting-button').click();
await page.getByTestId('workspace-setting:embedding').click();
await page.getByTestId('workspace-embedding-setting-header').waitFor({
@@ -18,7 +21,7 @@ export class SettingsPanelUtils {
if (
await page.getByTestId('workspace-embedding-setting-wrapper').isVisible()
) {
await page.getByTestId('modal-close-button').click();
await page.keyboard.press('Escape');
await page.getByTestId('workspace-embedding-setting-wrapper').waitFor({
state: 'hidden',
});
@@ -161,13 +164,22 @@ export class SettingsPanelUtils {
const searcher = await page.getByTestId('doc-selector-layout');
const searchInput = await page.getByTestId('doc-selector-search-input');
await searchInput.waitFor({ state: 'visible' });
await searchInput.focus();
await page.keyboard.insertText(doc);
const pageListItem = searcher.getByTestId('doc-list-item');
await expect(pageListItem).toHaveCount(1);
const pageListItemTitle = pageListItem.getByTestId('doc-list-item-title');
await expect(pageListItemTitle).toHaveText(doc);
const pageListItem = searcher
.getByTestId('doc-list-item')
.filter({
has: page
.getByTestId('doc-list-item-title')
.getByText(doc, { exact: true }),
})
.first();
await expect(pageListItem).toBeVisible();
await expect(pageListItem.getByTestId('doc-list-item-title')).toHaveText(
doc
);
await pageListItem.click();
await searcher.getByTestId('doc-selector-confirm-button').click();
@@ -200,7 +212,7 @@ export class SettingsPanelUtils {
await searchInput.focus();
await page.keyboard.insertText(doc);
const pageListItem = searcher.getByTestId('page-list-item');
const pageListItem = searcher.getByTestId('doc-list-item');
await expect(pageListItem).toHaveCount(1);
await pageListItem.getByTestId('affine-checkbox').uncheck();
+6 -3
View File
@@ -37,9 +37,12 @@ export async function waitForAllPagesLoad(page: Page) {
}
export async function clickNewPageButton(page: Page, title?: string) {
await page.getByTestId('sidebar-new-page-button').click({
// default timeout is 5000ms, but it's not enough for the CI first page load
timeout: 20000,
const newPageButton = page.getByTestId('sidebar-new-page-button');
await expect(newPageButton).toBeVisible({
timeout: 30000,
});
await newPageButton.click({
timeout: 30000,
});
await waitForEmptyEditor(page);
if (title) {