chore: strip data-testid on prod

This commit is contained in:
nathan
2025-09-05 14:15:32 +08:00
parent 10b3864805
commit 5e1d2e5345
3 changed files with 111 additions and 84 deletions

View File

@@ -1,7 +1,7 @@
import { v4 as uuidv4 } from 'uuid';
import { AuthTestUtils } from '../../support/auth-utils';
import { TestTool } from '../../support/page-utils';
import { PageSelectors, ViewActionSelectors, SpaceSelectors, waitForReactUpdate } from '../../support/selectors';
import { PageSelectors, waitForReactUpdate } from '../../support/selectors';
describe('More Page Actions', () => {
const APPFLOWY_BASE_URL = Cypress.env('APPFLOWY_BASE_URL');
@@ -27,52 +27,52 @@ describe('More Page Actions', () => {
// Sign in first
cy.visit('/login', { failOnStatusCode: false });
cy.wait(2000);
const authUtils = new AuthTestUtils();
authUtils.signInWithTestUrl(testEmail);
cy.url().should('include', '/app');
TestTool.waitForPageLoad(3000);
// Wait for the sidebar to load properly
TestTool.waitForSidebarReady();
cy.wait(2000);
// Skip expanding space since Getting started is already visible
cy.task('log', 'Page already visible, skipping expand');
// Open the first available page from the sidebar, then trigger inline ViewActionsPopover via "..." on the row
// Find the Getting started page and hover to reveal the more actions
cy.task('log', 'Looking for Getting started page');
// Find the page by its text content
cy.contains('Getting started')
.parent()
.parent()
.trigger('mouseenter', { force: true })
.trigger('mouseover', { force: true });
cy.wait(1000);
// Look for the more actions button - using PageSelectors
PageSelectors.moreActionsButton().first().click({ force: true });
cy.task('log', 'Clicked more actions button');
// Verify core items in ViewActionsPopover
// The menu should be open now, verify at least one of the common actions exists
cy.get('[data-slot="dropdown-menu-content"]', { timeout: 5000 }).should('exist');
// Check for common menu items - they might have different test ids or text
cy.get('[data-slot="dropdown-menu-content"]').within(() => {
// Look for items by text content since test ids might vary
cy.contains('Delete').should('exist');
cy.contains('Duplicate').should('exist');
cy.contains('Duplicate').should('exist');
cy.contains('Move to').should('exist');
});
});
it('should trigger Duplicate action from More actions menu', () => {
it('should trigger Duplicate action from More actions menu', () => {
// Handle uncaught exceptions during workspace creation
cy.on('uncaught:exception', (err: Error) => {
if (err.message.includes('No workspace or service found')) {
@@ -84,114 +84,56 @@ describe('More Page Actions', () => {
// Sign in first
cy.visit('/login', { failOnStatusCode: false });
cy.wait(2000);
const authUtils = new AuthTestUtils();
authUtils.signInWithTestUrl(testEmail);
cy.url().should('include', '/app');
TestTool.waitForPageLoad(3000);
// Wait for the sidebar to load properly
TestTool.waitForSidebarReady();
cy.wait(2000);
// Find the Getting started page and open its more actions menu
const originalPageName = 'Getting started';
cy.task('log', `Opening More Actions for page: ${originalPageName}`);
// Find the page by its text content and hover
cy.contains(originalPageName)
.parent()
.parent()
.trigger('mouseenter', { force: true })
.trigger('mouseover', { force: true });
cy.wait(1000);
// Look for the more actions button - using PageSelectors
PageSelectors.moreActionsButton().first().click({ force: true });
cy.task('log', 'Clicked more actions button');
// Click on Duplicate option which is available in the dropdown
cy.get('[data-slot="dropdown-menu-content"]').within(() => {
cy.contains('Duplicate').click();
});
cy.task('log', 'Clicked Duplicate option');
// Wait for the duplication to complete
waitForReactUpdate(2000);
// Verify the page was duplicated - there should now be two pages with similar names
// The duplicated page usually has "(copy)" or similar suffix
cy.contains('Getting started').should('exist');
// Check if there's a duplicated page (might have a suffix like "(1)" or "(copy)")
PageSelectors.names().then(($pages: JQuery<HTMLElement>) => {
const pageCount = $pages.filter((index: number, el: HTMLElement) =>
const pageCount = $pages.filter((index: number, el: HTMLElement) =>
el.textContent?.includes('Getting started')).length;
expect(pageCount).to.be.at.least(1);
cy.task('log', `Found ${pageCount} pages with 'Getting started' in the name`);
});
cy.task('log', 'Page successfully duplicated');
});
// it('should open Change Icon popover from More actions', () => {
// });
// it('should remove icon via Change Icon popover', () => {
// TestTool.morePageActionsChangeIcon();
// cy.get('[role="dialog"]').within(() => {
// cy.contains('button', 'Remove').click();
// });
// TestTool.getModal().should('not.exist');
// cy.get('.view-icon').should('not.exist');
// });
// it('should upload a custom icon via Change Icon popover', () => {
// TestTool.morePageActionsChangeIcon();
// cy.get('[role="dialog"]').within(() => {
// cy.get('input[type="file"]').attachFile('test-icon.png');
// });
// TestTool.getModal().should('not.exist');
// cy.get('.view-icon').should('exist');
// });
// it('should open page in a new tab from More actions', () => {
// cy.window().then((win) => {
// cy.stub(win, 'open').as('open');
// });
// TestTool.morePageActionsOpenNewTab();
// cy.get('@open').should('be.called');
// });
// it('should duplicate page from More actions and show success toast', () => {
// TestTool.morePageActionsDuplicate();
// cy.wait(2000);
// TestTool.getPageByName('Get started').should('have.length', 2);
// });
// it('should move page to another space from More actions', () => {
// TestTool.morePageActionsMoveTo();
// cy.get('[role="dialog"]').within(() => {
// TestTool.getSpaceItems().first().click();
// });
// cy.wait(2000);
// TestTool.getPageByName('Get started').should('be.visible');
// });
// it('should delete page from More actions and confirm deletion', () => {
// TestTool.morePageActionsDelete();
// TestTool.confirmPageDeletion();
// cy.wait(2000);
// TestTool.getPageByName('Get started').should('not.exist');
// });
});

View File

@@ -0,0 +1,82 @@
import { Plugin } from 'vite';
/**
* Vite plugin to strip data-testid attributes from production builds
* This reduces bundle size and removes test-specific attributes from production code
*/
export function stripTestIdPlugin(): Plugin {
return {
name: 'strip-test-id',
apply: 'build', // Only apply during build, not dev
transform(code: string, id: string) {
// Skip node_modules to avoid transforming external libraries
if (id.includes('node_modules')) {
return null;
}
// Only process .tsx and .jsx files from our source
if (!id.match(/src.*\.(tsx|jsx)$/)) {
return null;
}
// Only strip in production builds
if (process.env.NODE_ENV !== 'production') {
return null;
}
let transformedCode = code;
let hasChanges = false;
try {
// Pattern 1: Simple string attributes: data-testid="value" or data-testid='value'
// This is the safest pattern to remove
const simpleStringPattern = /\s+data-testid\s*=\s*["'][^"']*["']/g;
const matches = transformedCode.match(simpleStringPattern);
if (matches && matches.length > 0) {
console.log(`Stripping ${matches.length} data-testid attributes from ${id}`);
transformedCode = transformedCode.replace(simpleStringPattern, '');
hasChanges = true;
}
// Pattern 2: Simple expressions without nested braces: data-testid={variable}
const simpleExpressionPattern = /\s+data-testid\s*=\s*\{[^{}]+\}/g;
const exprMatches = transformedCode.match(simpleExpressionPattern);
if (exprMatches && exprMatches.length > 0) {
console.log(`Stripping ${exprMatches.length} data-testid expressions from ${id}`);
transformedCode = transformedCode.replace(simpleExpressionPattern, '');
hasChanges = true;
}
// Pattern 3: Template literals: data-testid={`value-${id}`}
const templatePattern = /\s+data-testid\s*=\s*\{`[^`]*`\}/g;
if (templatePattern.test(transformedCode)) {
transformedCode = transformedCode.replace(templatePattern, '');
hasChanges = true;
}
if (hasChanges) {
// Quick validation: check for obvious syntax errors
// Count opening and closing braces to ensure we didn't break anything
const openBraces = (transformedCode.match(/\{/g) || []).length;
const closeBraces = (transformedCode.match(/\}/g) || []).length;
if (openBraces !== closeBraces) {
console.warn(`Warning: Brace mismatch after transformation in ${id}. Skipping transformation.`);
return null;
}
return {
code: transformedCode,
map: null,
};
}
} catch (error) {
console.error(`Error processing ${id}:`, error);
// Return null to skip transformation on error
return null;
}
return null;
},
};
}

View File

@@ -7,6 +7,7 @@ import { createHtmlPlugin } from 'vite-plugin-html';
import istanbul from 'vite-plugin-istanbul';
import svgr from 'vite-plugin-svgr';
import { totalBundleSize } from 'vite-plugin-total-bundle-size';
import { stripTestIdPlugin } from './vite-plugin-strip-testid';
const resourcesPath = path.resolve(__dirname, '../resources');
const isDev = process.env.NODE_ENV === 'development';
@@ -17,6 +18,8 @@ const isTest = process.env.NODE_ENV === 'test' || process.env.COVERAGE === 'true
export default defineConfig({
plugins: [
react(),
// Strip data-testid attributes in production builds
isProd ? stripTestIdPlugin() : undefined,
createHtmlPlugin({
inject: {
data: {