chore: data id

This commit is contained in:
annie
2025-09-02 21:20:06 +08:00
parent 639026b4ae
commit d5d969eb41
8 changed files with 206 additions and 73 deletions

View File

@@ -1,6 +1,7 @@
import { v4 as uuidv4 } from 'uuid';
import { AuthTestUtils } from '../../support/auth-utils';
import { TestTool } from '../../support/page-utils';
import { PageSelectors, ModalSelectors, waitForReactUpdate } from '../../support/selectors';
describe('Page Edit Tests', () => {
const APPFLOWY_BASE_URL = Cypress.env('APPFLOWY_BASE_URL');
@@ -53,14 +54,14 @@ describe('Page Edit Tests', () => {
cy.task('log', `Target page name: ${testPageName}`);
// Click new page button
cy.get('[data-testid="new-page-button"]').should('be.visible').click();
cy.wait(1000);
PageSelectors.newPageButton().should('be.visible').click();
waitForReactUpdate(1000);
// Handle the new page modal
cy.get('[data-testid="new-page-modal"]').should('be.visible').within(() => {
ModalSelectors.newPageModal().should('be.visible').within(() => {
// Select the first available space
cy.get('[data-testid="space-item"]').first().click();
cy.wait(500);
ModalSelectors.spaceItemInModal().first().click();
waitForReactUpdate(500);
// Click Add button
cy.contains('button', 'Add').click();
});

View File

@@ -1,6 +1,7 @@
import { AuthTestUtils } from 'cypress/support/auth-utils';
import { uuidv4 } from 'lib0/random';
import { TestTool } from '../../support/page-utils';
import { PageSelectors, ViewActionSelectors, SpaceSelectors, waitForReactUpdate } from '../../support/selectors';
describe('More Page Actions', () => {
const APPFLOWY_BASE_URL = Cypress.env('APPFLOWY_BASE_URL');
@@ -33,11 +34,11 @@ describe('More Page Actions', () => {
cy.task('log', 'Expanded space');
// // Wait for pages to render
// cy.get('[data-testid="page-name"]', { timeout: 20000 }).should('exist');
// PageSelectors.names(), { timeout: 20000 }).should('exist');
// cy.task('log', 'Pages rendered');
// Open the first available page from the sidebar, then trigger inline ViewActionsPopover via "..." on the row
cy.get('[data-testid="page-name"]', { timeout: 30000 }).should('exist').first().invoke('text').then((raw) => {
PageSelectors.names(), { timeout: 30000 }).should('exist').first().invoke('text').then((raw) => {
const pageName = (raw || '').trim();
cy.task('log', `Opening ViewActionsPopover for page: ${pageName}`);
TestTool.openViewActionsPopoverForPage(pageName);
@@ -69,11 +70,11 @@ describe('More Page Actions', () => {
cy.wait(2000); // Wait for app to load
// Expand space if needed by clicking on it
cy.get('[data-testid="space-name"]').first().parent().parent().click({ force: true });
SpaceSelectors.names().first().parent().parent().click({ force: true });
cy.wait(500);
// Get the first page and open its more actions menu
cy.get('[data-testid="page-name"]', { timeout: 30000 }).should('exist').first().invoke('text').then((raw) => {
PageSelectors.names(), { timeout: 30000 }).should('exist').first().invoke('text').then((raw) => {
const originalPageName = (raw || '').trim();
cy.task('log', `Opening More Actions for page: ${originalPageName}`);
@@ -82,7 +83,7 @@ describe('More Page Actions', () => {
// Click on Rename option
cy.get('[data-slot="dropdown-menu-content"]').within(() => {
cy.get('[data-testid="more-page-rename"]').click();
ViewActionSelectors.renameButton().click();
});
cy.task('log', 'Clicked Rename option');
@@ -92,7 +93,7 @@ describe('More Page Actions', () => {
// Check if a modal opened or if it's inline editing
cy.get('body').then(($body) => {
const hasModal = $body.find('[role="dialog"]').length > 0;
const hasPageTitleInput = $body.find('[data-testid="page-title-input"]').length > 0;
const hasPageTitleInput = $body.find(PageSelectors.titleInput().selector).length > 0;
if (hasPageTitleInput) {
// It's a page title input (modal or inline)

View File

@@ -1,6 +1,7 @@
import { v4 as uuidv4 } from 'uuid';
import { AuthTestUtils } from '../../support/auth-utils';
import { TestTool } from '../../support/page-utils';
import { WorkspaceSelectors } from '../../support/selectors';
describe('User Feature Tests', () => {
const APPFLOWY_BASE_URL = Cypress.env('APPFLOWY_BASE_URL');
@@ -61,7 +62,7 @@ describe('User Feature Tests', () => {
cy.wait(500);
// Verify user email is displayed in the dropdown
cy.get('[data-testid="workspace-dropdown-content"]').within(() => {
WorkspaceSelectors.dropdownContent().within(() => {
cy.contains(randomEmail).should('be.visible');
});
cy.task('log', `Verified email ${randomEmail} is displayed in dropdown`);
@@ -76,7 +77,7 @@ describe('User Feature Tests', () => {
.should('have.length', 1);
// Verify workspace name is present
cy.get('[data-testid="workspace-item-name"]')
WorkspaceSelectors.itemName()
.should('exist')
.and('not.be.empty');
cy.task('log', 'Verified one workspace exists');

View File

@@ -3,6 +3,14 @@
* Contains high-level test flow operations that orchestrate multiple page interactions
*/
import {
PageSelectors,
SpaceSelectors,
ModalSelectors,
SidebarSelectors,
waitForReactUpdate
} from '../selectors';
/**
* Waits for the page to fully load
* @param waitTime - Time to wait in milliseconds (default: 3000)
@@ -20,7 +28,8 @@ export function waitForPageLoad(waitTime: number = 3000) {
*/
export function waitForSidebarReady(timeout: number = 10000) {
cy.task('log', 'Waiting for sidebar to be ready');
return cy.get('[data-testid="sidebar-page-header"]', { timeout }).should('be.visible');
return SidebarSelectors.pageHeader()
.should('be.visible', { timeout });
}
/**
@@ -39,18 +48,18 @@ export function createPageAndAddContent(pageName: string, content: string[]) {
// Handle WebSocket mock mode vs regular mode
if (Cypress.env('MOCK_WEBSOCKET')) {
cy.task('log', 'Opening first available page (WebSocket mock mode)');
cy.wait(2000);
cy.get('[data-testid="space-name"]').first().then(($space) => {
const $parent = $space.closest('[data-testid="space-item"]');
if ($parent.find('[data-testid="page-name"]:visible').length === 0) {
waitForReactUpdate(2000);
SpaceSelectors.names().first().then(($space) => {
const $parent = $space.closest(SpaceSelectors.items().selector);
if ($parent.find(PageSelectors.names().selector + ':visible').length === 0) {
cy.task('log', 'Expanding space to show pages');
cy.wrap($space).click();
cy.wait(500);
waitForReactUpdate(500);
}
});
cy.get('[data-testid="page-name"]:visible').first().click();
PageSelectors.names().filter(':visible').first().click();
cy.task('log', 'Waiting for page to load in WebSocket mock mode');
cy.wait(5000);
waitForReactUpdate(5000);
} else {
openPageFromSidebar(pageName);
}
@@ -58,7 +67,7 @@ export function createPageAndAddContent(pageName: string, content: string[]) {
cy.task('log', 'Opened page from sidebar');
typeLinesInVisibleEditor(content);
cy.task('log', 'Content typed successfully');
cy.wait(1000);
waitForReactUpdate(1000);
assertEditorContentEquals(content);
cy.task('log', 'Content verification completed');
}
@@ -72,17 +81,16 @@ export function openPageFromSidebar(pageName: string) {
cy.task('log', `Opening page from sidebar: ${pageName}`);
// Ensure sidebar is visible
cy.get('[data-testid="sidebar-page-header"]').should('be.visible');
SidebarSelectors.pageHeader().should('be.visible');
// Find and click the page
cy.get('[data-testid="page-name"]')
.contains(pageName)
PageSelectors.nameContaining(pageName)
.scrollIntoView()
.should('be.visible')
.click();
// Wait for page to load
cy.wait(2000);
waitForReactUpdate(2000);
cy.task('log', `Page "${pageName}" opened successfully`);
}
@@ -94,20 +102,20 @@ export function openPageFromSidebar(pageName: string) {
export function expandSpace(spaceIndex: number = 0) {
cy.task('log', `Expanding space at index ${spaceIndex}`);
cy.get('[data-testid="space-item"]').eq(spaceIndex).within(() => {
cy.get('[data-testid="space-expanded"]').then($expanded => {
SpaceSelectors.items().eq(spaceIndex).within(() => {
SpaceSelectors.expanded().then($expanded => {
const isExpanded = $expanded.attr('data-expanded') === 'true';
if (!isExpanded) {
cy.task('log', 'Space is collapsed, expanding it');
cy.get('[data-testid="space-name"]').click();
SpaceSelectors.names().first().click();
} else {
cy.task('log', 'Space is already expanded');
}
});
});
cy.wait(500);
waitForReactUpdate(500);
}
// Internal helper functions (not exported but used by exported functions)
@@ -120,27 +128,27 @@ function createPage(pageName: string) {
cy.task('log', `Creating page: ${pageName}`);
// Click new page button
cy.get('[data-testid="new-page-button"]').should('be.visible').click();
cy.wait(1000);
PageSelectors.newPageButton().should('be.visible').click();
waitForReactUpdate(1000);
// Handle the new page modal
cy.get('[data-testid="new-page-modal"]').should('be.visible').within(() => {
ModalSelectors.newPageModal().should('be.visible').within(() => {
// Select the first available space
cy.get('[data-testid="space-item"]').first().click();
cy.wait(500);
ModalSelectors.spaceItemInModal().first().click();
waitForReactUpdate(500);
// Click Add button
cy.contains('button', 'Add').click();
});
// Wait for navigation to the new page
cy.wait(3000);
waitForReactUpdate(3000);
// Close any modal dialogs
cy.get('body').then($body => {
if ($body.find('[role="dialog"]').length > 0) {
cy.task('log', 'Closing modal dialog');
cy.get('body').type('{esc}');
cy.wait(1000);
waitForReactUpdate(1000);
}
});
}
@@ -185,4 +193,45 @@ function assertEditorContentEquals(lines: string[]) {
cy.contains(line).should('exist');
cy.task('log', `✓ Found content: "${line}"`);
});
}
// Additional exported functions referenced in page-utils.ts
/**
* Closes the sidebar
* Referenced in page-utils.ts
*/
export function closeSidebar() {
cy.task('log', 'Closing sidebar');
// Implementation would depend on how sidebar is closed in the UI
// This is a placeholder to maintain compatibility
}
/**
* Creates a new page via backend quick action
* Referenced in page-utils.ts
*/
export function createNewPageViaBackendQuickAction(pageName?: string) {
cy.task('log', `Creating new page via backend quick action: ${pageName || 'unnamed'}`);
// Implementation would depend on the backend quick action flow
// This is a placeholder to maintain compatibility
}
/**
* Opens the command palette
* Referenced in page-utils.ts
*/
export function openCommandPalette() {
cy.task('log', 'Opening command palette');
// Implementation would depend on how command palette is opened
// This is a placeholder to maintain compatibility
}
/**
* Navigates to a specific route
* Referenced in page-utils.ts
*/
export function navigateTo(route: string) {
cy.task('log', `Navigating to: ${route}`);
cy.visit(route);
}

View File

@@ -3,6 +3,8 @@
* Contains functions for interacting with modal dialogs and popovers
*/
import { ShareSelectors, waitForReactUpdate } from '../selectors';
/**
* Opens the share popover for the current page
* Used in publish-page.cy.ts to access sharing and publishing options
@@ -12,15 +14,31 @@ export function openSharePopover() {
cy.task('log', 'Opening share popover');
// Click the share button in the page header
cy.get('[data-testid="share-button"]')
ShareSelectors.shareButton()
.should('be.visible')
.click();
// Wait for popover to open
cy.wait(1000);
waitForReactUpdate(1000);
// Verify popover is visible
cy.get('[data-testid="share-popover"]').should('be.visible');
ShareSelectors.sharePopover().should('be.visible');
cy.task('log', 'Share popover opened successfully');
}
/**
* Closes any open modal by clicking outside
* Referenced in page-utils.ts
*/
export function clickOutsideModal() {
cy.task('log', 'Clicking outside modal to close it');
// Click at the top-left corner of the page
cy.get('body').click(0, 0);
// Wait for modal to close
waitForReactUpdate(500);
cy.task('log', 'Modal closed');
}

View File

@@ -3,6 +3,8 @@
* Contains functions for interacting with pages in the sidebar
*/
import { PageSelectors, waitForReactUpdate } from '../selectors';
/**
* Gets a page element by its name
* Used in more-page-action.cy.ts for finding specific pages
@@ -11,9 +13,7 @@
*/
export function getPageByName(pageName: string) {
cy.task('log', `Getting page by name: ${pageName}`);
return cy.get('[data-testid="page-name"]')
.contains(pageName)
.closest('[data-testid="page-item"]');
return PageSelectors.itemByName(pageName);
}
/**
@@ -23,7 +23,7 @@ export function getPageByName(pageName: string) {
*/
export function getPageTitleInput() {
cy.task('log', 'Getting page title input element');
return cy.get('[data-testid="page-title-input"]').first();
return PageSelectors.titleInput().first();
}
/**
@@ -33,5 +33,28 @@ export function getPageTitleInput() {
export function savePageTitle() {
cy.task('log', 'Saving page title');
cy.focused().type('{enter}');
cy.wait(1000); // Wait for save to complete
waitForReactUpdate(1000); // Wait for save to complete
}
/**
* Opens the more actions menu for a specific page
* Referenced in page-utils.ts
* @param pageName - The name of the page
*/
export function openPageMoreActions(pageName: string) {
cy.task('log', `Opening more actions for page: ${pageName}`);
// Find the page and trigger hover to show actions
PageSelectors.nameContaining(pageName)
.parent()
.parent()
.trigger('mouseenter', { force: true });
// Wait for the button to appear
waitForReactUpdate(500);
// Click the more actions button
PageSelectors.moreActionsButton(pageName)
.should('exist')
.click({ force: true });
}

View File

@@ -3,6 +3,8 @@
* Contains functions for publishing pages and verifying published content
*/
import { ShareSelectors, waitForReactUpdate } from '../selectors';
/**
* Publishes the currently open page
* Used in publish-page.cy.ts to make pages publicly accessible
@@ -12,19 +14,19 @@ export function publishCurrentPage() {
cy.task('log', '=== Publishing Current Page ===');
// Open share popover
cy.get('[data-testid="share-button"]').should('be.visible').click();
cy.wait(1000);
ShareSelectors.shareButton().should('be.visible').click();
waitForReactUpdate(1000);
// Enable publishing
cy.get('[data-testid="publish-tab-button"]').click();
cy.wait(500);
ShareSelectors.publishTabButton().click();
waitForReactUpdate(500);
// Toggle publish switch
cy.get('[data-testid="publish-switch"]').click();
cy.wait(2000);
ShareSelectors.publishSwitch().click();
waitForReactUpdate(2000);
// Get the published URL
return cy.get('[data-testid="publish-url-input"]')
return ShareSelectors.publishUrlInput()
.should('be.visible')
.invoke('val')
.then((url) => {
@@ -41,7 +43,7 @@ export function publishCurrentPage() {
export function readPublishUrlFromPanel() {
cy.task('log', 'Reading publish URL from panel');
return cy.get('[data-testid="publish-url-input"]')
return ShareSelectors.publishUrlInput()
.should('be.visible')
.invoke('val')
.then((url) => {
@@ -59,7 +61,7 @@ export function verifyPublishedContentMatches(expectedContent: string[]) {
cy.task('log', `=== Verifying Published Content ===`);
// The page should already be loaded, just verify content
cy.wait(2000);
waitForReactUpdate(2000);
// Verify each content line exists
expectedContent.forEach(content => {
@@ -79,20 +81,20 @@ export function unpublishCurrentPageAndVerify(publishUrl: string) {
cy.task('log', '=== Unpublishing Current Page ===');
// Open share popover
cy.get('[data-testid="share-button"]').should('be.visible').click();
cy.wait(1000);
ShareSelectors.shareButton().should('be.visible').click();
waitForReactUpdate(1000);
// Go to publish tab
cy.get('[data-testid="publish-tab-button"]').click();
cy.wait(500);
ShareSelectors.publishTabButton().click();
waitForReactUpdate(500);
// Toggle publish switch off
cy.get('[data-testid="publish-switch"]').click();
cy.wait(2000);
ShareSelectors.publishSwitch().click();
waitForReactUpdate(2000);
// Close the popover
cy.get('body').type('{esc}');
cy.wait(1000);
waitForReactUpdate(1000);
// Verify the page is no longer accessible
cy.task('log', `Verifying ${publishUrl} is no longer accessible`);
@@ -116,20 +118,20 @@ export function unpublishFromSettingsAndVerify(publishUrl: string, pageName?: st
cy.task('log', '=== Unpublishing from Settings ===');
// Open settings/share panel
cy.get('[data-testid="page-settings-button"]').click();
cy.wait(1000);
ShareSelectors.pageSettingsButton().click();
waitForReactUpdate(1000);
// Navigate to publish settings
cy.get('[data-testid="publish-settings-tab"]').click();
cy.wait(500);
ShareSelectors.publishSettingsTab().click();
waitForReactUpdate(500);
// Click unpublish button
cy.get('[data-testid="unpublish-button"]').click();
cy.wait(1000);
ShareSelectors.unpublishButton().click();
waitForReactUpdate(1000);
// Confirm unpublish
cy.get('[data-testid="confirm-unpublish-button"]').click();
cy.wait(2000);
ShareSelectors.confirmUnpublishButton().click();
waitForReactUpdate(2000);
// Verify the page is no longer accessible
cy.task('log', `Verifying ${publishUrl} is no longer accessible`);
@@ -140,4 +142,20 @@ export function unpublishFromSettingsAndVerify(publishUrl: string, pageName?: st
expect(response.status).to.not.equal(200);
cy.task('log', `✓ Published page is no longer accessible (status: ${response.status})`);
});
}
/**
* Opens the share link in the same tab
* Used in share-publish.cy.ts (though not exported from page-utils.ts anymore)
*/
export function openShareLink(shareUrl: string) {
cy.task('log', `Opening share link: ${shareUrl}`);
// Visit the share URL
cy.visit(shareUrl);
// Wait for the page to load
cy.url().should('include', '/publish');
cy.task('log', 'Share link opened successfully');
}

View File

@@ -3,14 +3,16 @@
* Contains functions for interacting with workspace dropdown and settings
*/
import { WorkspaceSelectors, waitForReactUpdate } from '../selectors';
/**
* Opens the workspace dropdown menu
* Used in user.cy.ts to access workspace options
*/
export function openWorkspaceDropdown() {
cy.task('log', 'Opening workspace dropdown');
cy.get('[data-testid="workspace-dropdown-trigger"]').click();
cy.wait(500);
WorkspaceSelectors.dropdownTrigger().click();
waitForReactUpdate(500);
}
/**
@@ -20,7 +22,7 @@ export function openWorkspaceDropdown() {
*/
export function getWorkspaceItems() {
cy.task('log', 'Getting workspace items from dropdown');
return cy.get('[data-testid="workspace-item"]');
return WorkspaceSelectors.item();
}
/**
@@ -31,7 +33,7 @@ export function getWorkspaceItems() {
export function getWorkspaceMemberCounts() {
cy.task('log', 'Getting workspace member counts');
return cy.get('[data-testid="workspace-member-count"]')
return WorkspaceSelectors.memberCount()
.then($elements => {
const counts = [];
$elements.each((index, el) => {
@@ -40,4 +42,24 @@ export function getWorkspaceMemberCounts() {
cy.task('log', `Found member counts: ${counts.join(', ')}`);
return cy.wrap(counts);
});
}
/**
* Creates a new workspace with the given name
* This function is referenced in page-utils.ts but implementation may vary
*/
export function createWorkspace(workspaceName: string) {
cy.task('log', `Creating workspace: ${workspaceName}`);
// Implementation would go here based on the actual UI flow
// This is a placeholder to maintain compatibility
}
/**
* Returns the URL for a workspace
* This function is referenced in page-utils.ts
*/
export function workspaceUrl(workspaceName: string): string {
// Implementation would return the actual workspace URL
// This is a placeholder to maintain compatibility
return `/workspace/${workspaceName}`;
}