diff --git a/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx b/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx
index a21017b2a..09cf0f050 100644
--- a/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx
+++ b/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx
@@ -84,6 +84,7 @@ const WorkbenchTab = ({
return (
{
unsub = helperProcessManager.connectRenderer(view.webContents);
});
- view.webContents.on('did-start-loading', () => {
+ view.webContents.on('will-navigate', () => {
// means the view is reloaded
this.appTabsUIReady$.next(
new Set([...this.appTabsUIReady$.value].filter(key => key !== viewId))
diff --git a/tests/affine-desktop/e2e/basic.spec.ts b/tests/affine-desktop/e2e/basic.spec.ts
index b4f559fff..cf356925a 100644
--- a/tests/affine-desktop/e2e/basic.spec.ts
+++ b/tests/affine-desktop/e2e/basic.spec.ts
@@ -1,7 +1,6 @@
import { test } from '@affine-test/kit/electron';
import {
clickNewPageButton,
- createLinkedPage,
getBlockSuiteEditorTitle,
} from '@affine-test/kit/utils/page-logic';
import {
@@ -143,42 +142,3 @@ test('delete workspace', async ({ page }) => {
'Demo Workspace'
);
});
-
-// temporary way to enable split view
-async function enableSplitView(page: Page) {
- await page.evaluate(() => {
- const settingKey = 'affine-settings';
- window.localStorage.setItem(
- settingKey,
- JSON.stringify({
- clientBorder: false,
- fullWidthLayout: false,
- windowFrameStyle: 'frameless',
- fontStyle: 'Serif',
- dateFormat: 'MM/dd/YYYY',
- startWeekOnMonday: false,
- enableBlurBackground: true,
- enableNoisyBackground: true,
- autoCheckUpdate: true,
- autoDownloadUpdate: true,
- enableMultiView: true,
- editorFlags: {},
- })
- );
- });
- await page.reload();
-}
-
-test('open split view', async ({ page }) => {
- await enableSplitView(page);
- await clickNewPageButton(page);
- await page.waitForTimeout(500);
- await page.keyboard.press('Enter');
- await createLinkedPage(page, 'hi from another page');
- await page
- .locator('.affine-reference-title:has-text("hi from another page")')
- .click({
- modifiers: ['ControlOrMeta', 'Alt'],
- });
- await expect(page.locator('.doc-title-container')).toHaveCount(2);
-});
diff --git a/tests/affine-desktop/e2e/behavior.spec.ts b/tests/affine-desktop/e2e/behavior.spec.ts
index a060608cb..7231b5a54 100644
--- a/tests/affine-desktop/e2e/behavior.spec.ts
+++ b/tests/affine-desktop/e2e/behavior.spec.ts
@@ -3,7 +3,7 @@ import os from 'node:os';
import { test } from '@affine-test/kit/electron';
import { shouldCallIpcRendererHandler } from '@affine-test/kit/utils/ipc';
-test.describe('behavior test', () => {
+test.describe.skip('behavior test', () => {
if (os.platform() === 'darwin') {
test('system button should hidden correctly', async ({
page,
diff --git a/tests/affine-desktop/e2e/tabs.spec.ts b/tests/affine-desktop/e2e/tabs.spec.ts
new file mode 100644
index 000000000..fba462a85
--- /dev/null
+++ b/tests/affine-desktop/e2e/tabs.spec.ts
@@ -0,0 +1,190 @@
+import { test } from '@affine-test/kit/electron';
+import {
+ clickNewPageButton,
+ createLinkedPage,
+} from '@affine-test/kit/utils/page-logic';
+import { expect, type Page } from '@playwright/test';
+
+test('create new tab', async ({ views }) => {
+ let page = await views.getActive();
+
+ await page.getByTestId('add-tab-view-button').click();
+ await expect(page.getByTestId('workbench-tab')).toHaveCount(2);
+ // new tab title should be All docs
+ await expect(page.getByTestId('workbench-tab').nth(1)).toContainText(
+ 'All docs'
+ );
+ page = await views.getActive();
+ // page content should be at all docs page
+ await expect(page.getByTestId('virtualized-page-list')).toContainText(
+ 'All Docs'
+ );
+});
+
+test('can switch & close tab by clicking', async ({ page }) => {
+ await page.getByTestId('add-tab-view-button').click();
+
+ await expect(page.getByTestId('workbench-tab').nth(1)).toHaveAttribute(
+ 'data-active',
+ 'true'
+ );
+ // switch to the previous tab by clicking on it
+ await page.getByTestId('workbench-tab').nth(0).click();
+ await expect(page.getByTestId('workbench-tab').nth(0)).toHaveAttribute(
+ 'data-active',
+ 'true'
+ );
+
+ // switch to the next tab by clicking on it
+ await page.getByTestId('workbench-tab').nth(1).click();
+ await expect(page.getByTestId('workbench-tab').nth(1)).toHaveAttribute(
+ 'data-active',
+ 'true'
+ );
+
+ // close the current tab
+ await page
+ .getByTestId('workbench-tab')
+ .nth(1)
+ .getByTestId('close-tab-button')
+ .click();
+
+ // the first tab should be active
+ await expect(page.getByTestId('workbench-tab').nth(0)).toHaveAttribute(
+ 'data-active',
+ 'true'
+ );
+});
+
+test('can switch tab by CTRL+number', async ({ page }) => {
+ test.fixme(); // the shortcut can be only captured by the main process
+ await page.keyboard.down('ControlOrMeta+T');
+ await expect(page.getByTestId('workbench-tab')).toHaveCount(2);
+ await expect(page.getByTestId('workbench-tab').nth(1)).toHaveAttribute(
+ 'data-active',
+ 'true'
+ );
+ // switch to the previous tab by pressing CTRL+1
+ await page.locator('body').press('ControlOrMeta+1');
+ await expect(page.getByTestId('workbench-tab').nth(0)).toHaveAttribute(
+ 'data-active',
+ 'true'
+ );
+ // switch to the next tab by pressing CTRL+2
+ await page.locator('body').press('ControlOrMeta+2');
+ await expect(page.getByTestId('workbench-tab').nth(1)).toHaveAttribute(
+ 'data-active',
+ 'true'
+ );
+});
+
+test('Collapse Sidebar', async ({ page }) => {
+ await page
+ .locator('[data-testid=app-sidebar-arrow-button-collapse][data-show=true]')
+ .click();
+ const sliderBarArea = page.getByTestId('app-sidebar');
+ await expect(sliderBarArea).not.toBeInViewport();
+});
+
+test('Expand Sidebar', async ({ page }) => {
+ await page
+ .locator('[data-testid=app-sidebar-arrow-button-collapse][data-show=true]')
+ .click();
+ const sliderBarArea = page.getByTestId('sliderBar-inner');
+ await expect(sliderBarArea).not.toBeInViewport();
+
+ await page
+ .locator('[data-testid=app-sidebar-arrow-button-expand][data-show=true]')
+ .click();
+ await expect(sliderBarArea).toBeInViewport();
+});
+
+test('tab title will change when navigating', async ({ page }) => {
+ await expect(page.getByTestId('workbench-tab')).toContainText(
+ 'Write, Draw, Plan all at Once'
+ );
+
+ // create new page
+ await clickNewPageButton(page);
+ await expect(page.getByTestId('workbench-tab')).toContainText('Untitled');
+
+ // go to all page
+ await page.getByTestId('all-pages').click();
+ await expect(page.getByTestId('workbench-tab')).toContainText('All docs');
+
+ // go to today's journal
+ await page.getByTestId('slider-bar-journals-button').click();
+ await expect(page.locator('.doc-title-container')).toContainText('Today');
+ const dateString = await page
+ .locator('.doc-title-container > span:first-of-type')
+ .textContent();
+
+ if (dateString) {
+ await expect(page.getByTestId('workbench-tab')).toContainText(dateString);
+ }
+});
+
+// temporary way to enable split view
+async function enableSplitView(page: Page) {
+ await page.evaluate(() => {
+ const settingKey = 'affine-settings';
+ window.localStorage.setItem(
+ settingKey,
+ JSON.stringify({
+ clientBorder: false,
+ fullWidthLayout: false,
+ windowFrameStyle: 'frameless',
+ fontStyle: 'Serif',
+ dateFormat: 'MM/dd/YYYY',
+ startWeekOnMonday: false,
+ enableBlurBackground: true,
+ enableNoisyBackground: true,
+ autoCheckUpdate: true,
+ autoDownloadUpdate: true,
+ enableMultiView: true,
+ editorFlags: {},
+ })
+ );
+ });
+ await page.reload();
+}
+
+test('open split view', async ({ page }) => {
+ await enableSplitView(page);
+ await clickNewPageButton(page);
+ await page.waitForTimeout(500);
+ await page.keyboard.press('Enter');
+ await createLinkedPage(page, 'hi from another page');
+ await page
+ .locator('.affine-reference-title:has-text("hi from another page")')
+ .click({
+ modifiers: ['ControlOrMeta', 'Alt'],
+ });
+ await expect(page.locator('.doc-title-container')).toHaveCount(2);
+
+ // check tab title
+ await expect(page.getByTestId('split-view-label')).toHaveCount(2);
+ await expect(page.getByTestId('split-view-label').nth(0)).toContainText(
+ 'Untitled'
+ );
+ await expect(page.getByTestId('split-view-label').nth(1)).toContainText(
+ 'hi from another page'
+ );
+
+ // the second split view should be active
+ await expect(page.getByTestId('split-view-label').nth(1)).toHaveAttribute(
+ 'data-active',
+ 'true'
+ );
+
+ // by clicking the first split view label, the first split view should be active
+ await page.getByTestId('split-view-label').nth(0).click();
+ await expect(page.getByTestId('split-view-label').nth(0)).toHaveAttribute(
+ 'data-active',
+ 'true'
+ );
+ await expect(page.getByTestId('split-view-indicator').nth(0)).toHaveAttribute(
+ 'data-active',
+ 'true'
+ );
+});
diff --git a/tests/kit/electron.ts b/tests/kit/electron.ts
index da293ac46..1707cfee7 100644
--- a/tests/kit/electron.ts
+++ b/tests/kit/electron.ts
@@ -47,6 +47,9 @@ export const test = base.extend<{
appData: string;
sessionData: string;
};
+ views: {
+ getActive: () => Promise
;
+ };
router: {
goto: (path: RoutePath) => Promise;
};
@@ -88,6 +91,15 @@ export const test = base.extend<{
await use(page as Page);
},
+ views: async ({ electronApp, page }, use) => {
+ void page; // makes sure page is a dependency
+ await use({
+ getActive: async () => {
+ const view = await getActivePage(electronApp.windows());
+ return view || page;
+ },
+ });
+ },
// eslint-disable-next-line no-empty-pattern
electronApp: async ({}, use) => {
try {
@@ -150,8 +162,3 @@ export const test = base.extend<{
await use(appInfo);
},
});
-
-// eslint-disable-next-line no-empty-pattern
-test.afterEach(({}, testInfo) => {
- console.log('cleaning up for ' + testInfo);
-});
diff --git a/tests/kit/utils/ipc.ts b/tests/kit/utils/ipc.ts
index 27a1f0961..e5f554469 100644
--- a/tests/kit/utils/ipc.ts
+++ b/tests/kit/utils/ipc.ts
@@ -1,3 +1,5 @@
+// TODO(@pengx17): remove
+
import type { affine } from '@affine/electron-api';
// Credit: https://github.com/spaceagetv/electron-playwright-helpers/blob/main/src/ipc_helpers.ts
import type { Page } from '@playwright/test';