test: support test e2e with OctoBase (#1593)
Co-authored-by: DarkSky <darksky2048@gmail.com>
This commit is contained in:
17
.github/workflows/build.yml
vendored
17
.github/workflows/build.yml
vendored
@@ -72,13 +72,28 @@ jobs:
|
|||||||
shard: [1, 2, 3, 4]
|
shard: [1, 2, 3, 4]
|
||||||
environment: development
|
environment: development
|
||||||
needs: build
|
needs: build
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
services:
|
||||||
|
octobase:
|
||||||
|
image: ghcr.io/toeverything/cloud:nightly-latest
|
||||||
|
ports:
|
||||||
|
- 3000:3000
|
||||||
|
env:
|
||||||
|
SIGN_KEY: 'test123'
|
||||||
|
RUST_LOG: 'debug'
|
||||||
|
JWST_DEV: '1'
|
||||||
|
credentials:
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.ACTIONS_PACKAGE_PUBLISH }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v2
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version: 18
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
- run: pnpm i
|
- run: pnpm i
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export const TransformWorkspaceToAffineModal: React.FC<
|
|||||||
{/* <StyleTips>{t('Retain cached cloud data')}</StyleTips> */}
|
{/* <StyleTips>{t('Retain cached cloud data')}</StyleTips> */}
|
||||||
<div>
|
<div>
|
||||||
<StyleButton
|
<StyleButton
|
||||||
|
data-testid="confirm-enable-cloud-button"
|
||||||
shape="round"
|
shape="round"
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ const AffineRemoteCollaborationPanel: React.FC<
|
|||||||
<ul>
|
<ul>
|
||||||
<StyledMemberTitleContainer>
|
<StyledMemberTitleContainer>
|
||||||
<StyledMemberNameContainer>
|
<StyledMemberNameContainer>
|
||||||
{t('Users')} ({members.length})
|
{t('Users')} (
|
||||||
|
<span data-testid="member-length">{members.length}</span>)
|
||||||
</StyledMemberNameContainer>
|
</StyledMemberNameContainer>
|
||||||
<StyledMemberRoleContainer>
|
<StyledMemberRoleContainer>
|
||||||
{t('Access level')}
|
{t('Access level')}
|
||||||
@@ -145,6 +146,7 @@ const AffineRemoteCollaborationPanel: React.FC<
|
|||||||
setIsInviteModalShow(true);
|
setIsInviteModalShow(true);
|
||||||
}}
|
}}
|
||||||
type="primary"
|
type="primary"
|
||||||
|
data-testid="invite-members"
|
||||||
shape="circle"
|
shape="circle"
|
||||||
>
|
>
|
||||||
{t('Invite Members')}
|
{t('Invite Members')}
|
||||||
@@ -176,6 +178,7 @@ const LocalCollaborationPanel: React.FC<
|
|||||||
<>
|
<>
|
||||||
<Wrapper marginBottom="42px">{t('Collaboration Description')}</Wrapper>
|
<Wrapper marginBottom="42px">{t('Collaboration Description')}</Wrapper>
|
||||||
<Button
|
<Button
|
||||||
|
data-testid="local-workspace-enable-cloud-button"
|
||||||
type="light"
|
type="light"
|
||||||
shape="circle"
|
shape="circle"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ interface LoginModalProps {
|
|||||||
onInviteSuccess: () => void;
|
onInviteSuccess: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const gmailReg = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@gmail\.com$/;
|
const gmailReg =
|
||||||
|
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@(gmail|example)\.(com|org)$/;
|
||||||
|
|
||||||
const Result: React.FC<{
|
const Result: React.FC<{
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
@@ -75,6 +76,7 @@ export const InviteMemberModal = ({
|
|||||||
<ContentTitle>{t('Invite Members')}</ContentTitle>
|
<ContentTitle>{t('Invite Members')}</ContentTitle>
|
||||||
<InviteBox>
|
<InviteBox>
|
||||||
<Input
|
<Input
|
||||||
|
data-testid="invite-member-input"
|
||||||
width={360}
|
width={360}
|
||||||
value={email}
|
value={email}
|
||||||
onChange={inputChange}
|
onChange={inputChange}
|
||||||
@@ -95,6 +97,7 @@ export const InviteMemberModal = ({
|
|||||||
</Content>
|
</Content>
|
||||||
<Footer>
|
<Footer>
|
||||||
<Button
|
<Button
|
||||||
|
data-testid="invite-member-button"
|
||||||
disabled={!gmailReg.test(email)}
|
disabled={!gmailReg.test(email)}
|
||||||
shape="circle"
|
shape="circle"
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|||||||
@@ -31,3 +31,12 @@ const bareAuth = createBareClient(prefixUrl);
|
|||||||
const googleAuth = new GoogleAuth(bareAuth);
|
const googleAuth = new GoogleAuth(bareAuth);
|
||||||
export const clientAuth = createAuthClient(bareAuth, googleAuth);
|
export const clientAuth = createAuthClient(bareAuth, googleAuth);
|
||||||
export const apis = getApis(bareAuth, clientAuth, googleAuth);
|
export const apis = getApis(bareAuth, clientAuth, googleAuth);
|
||||||
|
|
||||||
|
if (!globalThis.AFFINE_APIS) {
|
||||||
|
globalThis.AFFINE_APIS = apis;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
|
var AFFINE_APIS: ReturnType<typeof getApis>;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { resolve } from 'node:path';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
PlaywrightTestConfig,
|
PlaywrightTestConfig,
|
||||||
PlaywrightWorkerOptions,
|
PlaywrightWorkerOptions,
|
||||||
@@ -18,6 +20,7 @@ const config: PlaywrightTestConfig = {
|
|||||||
fullyParallel: true,
|
fullyParallel: true,
|
||||||
timeout: process.env.CI ? 50_000 : 30_000,
|
timeout: process.env.CI ? 50_000 : 30_000,
|
||||||
use: {
|
use: {
|
||||||
|
baseURL: 'http://localhost:8080/',
|
||||||
browserName:
|
browserName:
|
||||||
(process.env.BROWSER as PlaywrightWorkerOptions['browserName']) ??
|
(process.env.BROWSER as PlaywrightWorkerOptions['browserName']) ??
|
||||||
'chromium',
|
'chromium',
|
||||||
@@ -39,16 +42,31 @@ const config: PlaywrightTestConfig = {
|
|||||||
// See https://playwright.dev/docs/test-reporters#github-actions-annotations
|
// See https://playwright.dev/docs/test-reporters#github-actions-annotations
|
||||||
reporter: process.env.CI ? 'github' : 'list',
|
reporter: process.env.CI ? 'github' : 'list',
|
||||||
|
|
||||||
webServer: {
|
webServer: [
|
||||||
command: 'pnpm build && pnpm start -p 8080',
|
{
|
||||||
port: 8080,
|
command: 'cargo run -p affine-cloud',
|
||||||
timeout: 120 * 1000,
|
port: 3000,
|
||||||
reuseExistingServer: !process.env.CI,
|
timeout: 10 * 1000,
|
||||||
env: {
|
reuseExistingServer: true,
|
||||||
COVERAGE: process.env.COVERAGE || 'false',
|
cwd: process.env.OCTOBASE_CWD ?? resolve(process.cwd(), 'apps', 'server'),
|
||||||
ENABLE_DEBUG_PAGE: '1',
|
env: {
|
||||||
|
SIGN_KEY: 'test123',
|
||||||
|
RUST_LOG: 'debug',
|
||||||
|
JWST_DEV: '1',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
|
command: 'pnpm build && pnpm start -p 8080',
|
||||||
|
port: 8080,
|
||||||
|
timeout: 120 * 1000,
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
env: {
|
||||||
|
COVERAGE: process.env.COVERAGE || 'false',
|
||||||
|
ENABLE_DEBUG_PAGE: '1',
|
||||||
|
NODE_API_SERVER: 'local',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.env.CI) {
|
if (process.env.CI) {
|
||||||
|
|||||||
5
tests/fixtures/userA.json
vendored
Normal file
5
tests/fixtures/userA.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "Alex Yang",
|
||||||
|
"email": "alex.yang@example.org",
|
||||||
|
"password": "123456"
|
||||||
|
}
|
||||||
5
tests/fixtures/userB.json
vendored
Normal file
5
tests/fixtures/userB.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "Boying Li",
|
||||||
|
"email": "boying.li@example.org",
|
||||||
|
"password": "654321"
|
||||||
|
}
|
||||||
5
tests/libs/setting.ts
Normal file
5
tests/libs/setting.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import type { Page } from '@playwright/test';
|
||||||
|
|
||||||
|
export async function clickCollaborationPanel(page: Page) {
|
||||||
|
await page.click('[data-tab-key="collaboration"]');
|
||||||
|
}
|
||||||
@@ -1,5 +1,17 @@
|
|||||||
import type { Page } from '@playwright/test';
|
import type { Page } from '@playwright/test';
|
||||||
|
|
||||||
export async function clickSideBarSettingButton(page: Page) {
|
export async function clickSideBarSettingButton(page: Page) {
|
||||||
await page.getByTestId('slider-bar-workspace-setting-button').click();
|
return page.getByTestId('slider-bar-workspace-setting-button').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function clickSideBarAllPageButton(page: Page) {
|
||||||
|
return page.getByTestId('all-pages').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function clickSideBarCurrentWorkspaceBanner(page: Page) {
|
||||||
|
return page.getByTestId('current-workspace').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function clickNewPageButton(page: Page) {
|
||||||
|
return page.getByTestId('new-page-button').click();
|
||||||
}
|
}
|
||||||
|
|||||||
84
tests/libs/utils.ts
Normal file
84
tests/libs/utils.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import type { Page } from '@playwright/test';
|
||||||
|
|
||||||
|
import userA from '../fixtures/userA.json';
|
||||||
|
import userB from '../fixtures/userB.json';
|
||||||
|
|
||||||
|
export async function createFakeUser() {
|
||||||
|
try {
|
||||||
|
const response = await Promise.all([
|
||||||
|
fetch('http://127.0.0.1:3000/api/user/token', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
type: 'DebugLoginUser',
|
||||||
|
email: userA.email,
|
||||||
|
password: userA.password,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
fetch('http://127.0.0.1:3000/api/user/token', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
type: 'DebugLoginUser',
|
||||||
|
email: userB.email,
|
||||||
|
password: userB.password,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
return Promise.all(
|
||||||
|
response.map(res => {
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error('User not found');
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
const response = await Promise.all([
|
||||||
|
// Register user A
|
||||||
|
fetch('http://localhost:3000/api/user/token', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
type: 'DebugCreateUser',
|
||||||
|
...userA,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
// Register user B
|
||||||
|
fetch('http://localhost:3000/api/user/token', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
type: 'DebugCreateUser',
|
||||||
|
...userB,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
return Promise.all(response.map(res => res.json()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loginUser(
|
||||||
|
page: Page,
|
||||||
|
token: {
|
||||||
|
refresh: string;
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
await page.evaluate(async token => {
|
||||||
|
// @ts-ignore
|
||||||
|
globalThis.AFFINE_APIS.auth.setLogin(token);
|
||||||
|
}, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function openHomePage(page: Page) {
|
||||||
|
return page.goto('http://localhost:8080');
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { expect } from '@playwright/test';
|
|||||||
|
|
||||||
import { loadPage } from './libs/load-page';
|
import { loadPage } from './libs/load-page';
|
||||||
import { test } from './libs/playwright';
|
import { test } from './libs/playwright';
|
||||||
|
import { clickSideBarAllPageButton } from './libs/sidebar';
|
||||||
import { createWorkspace } from './libs/workspace-logic';
|
import { createWorkspace } from './libs/workspace-logic';
|
||||||
loadPage();
|
loadPage();
|
||||||
|
|
||||||
@@ -32,8 +33,7 @@ test.describe('Local first workspace list', () => {
|
|||||||
//check page list length
|
//check page list length
|
||||||
const closeWorkspaceModal = page.getByTestId('close-workspace-modal');
|
const closeWorkspaceModal = page.getByTestId('close-workspace-modal');
|
||||||
await closeWorkspaceModal.click();
|
await closeWorkspaceModal.click();
|
||||||
const allPageButton = page.getByTestId('all-pages');
|
await clickSideBarAllPageButton(page);
|
||||||
await allPageButton.click();
|
|
||||||
await page.waitForTimeout(1000);
|
await page.waitForTimeout(1000);
|
||||||
const pageList = page.locator('[data-testid=page-list-item]');
|
const pageList = page.locator('[data-testid=page-list-item]');
|
||||||
const result = await pageList.count();
|
const result = await pageList.count();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { expect } from '@playwright/test';
|
|||||||
|
|
||||||
import { loadPage } from './libs/load-page';
|
import { loadPage } from './libs/load-page';
|
||||||
import { test } from './libs/playwright';
|
import { test } from './libs/playwright';
|
||||||
|
import { clickSideBarCurrentWorkspaceBanner } from './libs/sidebar';
|
||||||
|
|
||||||
loadPage();
|
loadPage();
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ test.describe('Local first default workspace', () => {
|
|||||||
});
|
});
|
||||||
test.describe('Language switch', () => {
|
test.describe('Language switch', () => {
|
||||||
test('Open language switch menu', async ({ page }) => {
|
test('Open language switch menu', async ({ page }) => {
|
||||||
await page.getByTestId('current-workspace').click();
|
await clickSideBarCurrentWorkspaceBanner(page);
|
||||||
const languageMenuButton = page.getByTestId('language-menu-button');
|
const languageMenuButton = page.getByTestId('language-menu-button');
|
||||||
await expect(languageMenuButton).toBeVisible();
|
await expect(languageMenuButton).toBeVisible();
|
||||||
const actual = await languageMenuButton.innerText();
|
const actual = await languageMenuButton.innerText();
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
import { loadPage } from './libs/load-page';
|
|
||||||
import { test } from './libs/playwright';
|
|
||||||
|
|
||||||
loadPage();
|
|
||||||
|
|
||||||
test.describe('Login Flow', () => {
|
|
||||||
test.skip('Open login modal by click current workspace', async ({ page }) => {
|
|
||||||
await page.getByTestId('current-workspace').click();
|
|
||||||
await page.waitForTimeout(800);
|
|
||||||
// why don't we use waitForSelector, It seems that waitForSelector not stable?
|
|
||||||
await page.getByTestId('open-login-modal').click();
|
|
||||||
await page.waitForTimeout(800);
|
|
||||||
await page
|
|
||||||
.getByRole('heading', { name: 'Currently not logged in' })
|
|
||||||
.click();
|
|
||||||
});
|
|
||||||
// not stable
|
|
||||||
// test.skip('Open google firebase page', async ({ page }) => {
|
|
||||||
// await page.getByTestId('current-workspace').click();
|
|
||||||
// await page.waitForTimeout(800);
|
|
||||||
// // why don't we use waitForSelector, It seems that waitForSelector not stable?
|
|
||||||
// await page.getByTestId('open-login-modal').click();
|
|
||||||
// await page.waitForTimeout(800);
|
|
||||||
// const [firebasePage] = await Promise.all([
|
|
||||||
// page.waitForEvent('popup'),
|
|
||||||
// page
|
|
||||||
// .getByRole('button', {
|
|
||||||
// name: 'Google Continue with Google Set up an AFFiNE account to sync data',
|
|
||||||
// })
|
|
||||||
// .click(),
|
|
||||||
// ]);
|
|
||||||
|
|
||||||
// expect(firebasePage.url()).toContain('.firebaseapp.com/__/auth/handler');
|
|
||||||
// });
|
|
||||||
});
|
|
||||||
47
tests/parallels/affine-workspace.spec.ts
Normal file
47
tests/parallels/affine-workspace.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
import userA from '../fixtures/userA.json';
|
||||||
|
import { test } from '../libs/playwright';
|
||||||
|
import { clickCollaborationPanel } from '../libs/setting';
|
||||||
|
import {
|
||||||
|
clickNewPageButton,
|
||||||
|
clickSideBarAllPageButton,
|
||||||
|
clickSideBarCurrentWorkspaceBanner,
|
||||||
|
clickSideBarSettingButton,
|
||||||
|
} from '../libs/sidebar';
|
||||||
|
import { createFakeUser, loginUser, openHomePage } from '../libs/utils';
|
||||||
|
import { createWorkspace } from '../libs/workspace-logic';
|
||||||
|
|
||||||
|
test.describe('affine workspace', () => {
|
||||||
|
test('should login with user A', async ({ page }) => {
|
||||||
|
await openHomePage(page);
|
||||||
|
const [a] = await createFakeUser();
|
||||||
|
await loginUser(page, a);
|
||||||
|
await clickSideBarCurrentWorkspaceBanner(page);
|
||||||
|
const footer = page.locator('[data-testid="workspace-list-modal-footer"]');
|
||||||
|
expect(await footer.getByText(userA.name).isVisible()).toBe(true);
|
||||||
|
expect(await footer.getByText(userA.email).isVisible()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// fixme: skip this because of duplicated creation of workspace may cause error
|
||||||
|
test.skip('should enable affine workspace successfully', async ({ page }) => {
|
||||||
|
await openHomePage(page);
|
||||||
|
const [a] = await createFakeUser();
|
||||||
|
await loginUser(page, a);
|
||||||
|
await createWorkspace({ name: 'test1' }, page);
|
||||||
|
await page.waitForTimeout(50);
|
||||||
|
await clickSideBarSettingButton(page);
|
||||||
|
await page.waitForTimeout(50);
|
||||||
|
await clickCollaborationPanel(page);
|
||||||
|
await page.getByTestId('local-workspace-enable-cloud-button').click();
|
||||||
|
await page.getByTestId('confirm-enable-cloud-button').click();
|
||||||
|
await page.waitForSelector("[data-testid='member-length']", {
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
|
await clickSideBarAllPageButton(page);
|
||||||
|
await clickNewPageButton(page);
|
||||||
|
await page.locator('[data-block-is-title="true"]').type('Hello, world!', {
|
||||||
|
delay: 50,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user