diff --git a/comprehensive-demo-react18/README.md b/comprehensive-demo-react18/README.md index a4fa3a294b1..87aae5716be 100644 --- a/comprehensive-demo-react18/README.md +++ b/comprehensive-demo-react18/README.md @@ -18,10 +18,10 @@ Included apps: - App #5 (LitElement): [http://localhost:3005](http://localhost:3005) -# Running Cypress E2E Tests +# Running Playwright E2E Tests -To run tests in interactive mode, run `npm run cypress:debug` from the root directory of the project. It will open Cypress Test Runner and allow to run tests in interactive mode. [More info about "How to run tests"](../../cypress-e2e/README.md#how-to-run-tests) +To run the Playwright test suite locally in headless mode, execute `pnpm test:e2e` from this workspace. The tests automatically start the demo and verify each application. -To build app and run test in headless mode, run `yarn e2e:ci`. It will build app and run tests for this workspace in headless mode. If tets failed cypress will create `cypress` directory in sample root folder with screenshots and videos. +For an interactive UI to debug or explore tests, run `pnpm test:e2e:ui`. -["Best Practices, Rules amd more interesting information here](../../cypress-e2e/README.md) +In CI scenarios run `pnpm e2e:ci`. This command builds the applications, installs the required Playwright browsers and runs the tests with a concise reporter. diff --git a/comprehensive-demo-react18/e2e/checkApp1.spec.ts b/comprehensive-demo-react18/e2e/checkApp1.spec.ts new file mode 100644 index 00000000000..d25051560e7 --- /dev/null +++ b/comprehensive-demo-react18/e2e/checkApp1.spec.ts @@ -0,0 +1,190 @@ +import { test, expect } from '@playwright/test'; +import type { Page } from '@playwright/test'; + +const base = 'http://localhost:3001'; + +const demoPages = [ + { name: 'Main', hash: '#/' }, + { name: 'UI Library', hash: '#/ui-library' }, + { name: 'Dialog', hash: '#/dialog' }, + { name: 'Svelte Page', hash: '#/svelte' }, + { name: 'Routing', hash: '#/routing/foo' }, +]; + +const appLinks = [ + { name: 'App #1', href: 'http://localhost:3001' }, + { name: 'App #2', href: 'http://localhost:3002' }, + { name: 'App #3', href: 'http://localhost:3003' }, + { name: 'App #4', href: 'http://localhost:3004' }, + { name: 'App #5', href: 'http://localhost:3005' }, +]; + +const mainPageParagraphs = [ + 'Welcome to the Module Federation Demo!', + 'Click any of the items on the left to get started.', + 'Feel free to leave me feedback', +]; + +const uiLibraryParagraphs = [ + 'Simple example showing host app and external component using separate CSS solutions.', + 'This Button component can be found in App #3.', + 'This button is also used in the routing demo.', +]; + +const routingParagraphs = [ + 'The following tab components are being imported remotely from "bravo-app".', + "Notice that your browser's route is /routing/ depending on which tab is active.", + 'If you open http://localhost:3002 you will see the same tab components at the root level', + 'The "Bar" tab also lazily renders the styled-component Button from the UI Library demo only when rendered.', +]; + +const expectAppBar = async (page: Page, title: string) => { + const appBar = page.locator('header').first(); + await expect(appBar).toBeVisible(); + await expect(appBar).toHaveCSS('background-color', 'rgb(63, 81, 181)'); + await expect(page.getByRole('heading', { name: title })).toBeVisible(); +}; + +test.describe('Comprehensive Demo App1', () => { + test('main page displays sidebar links and elements', async ({ page }) => { + await page.goto(base); + + await expect(page.getByRole('heading', { name: 'SideNav' })).toBeVisible(); + await expect(page.getByText('Demo Pages')).toBeVisible(); + await expect(page.getByText('Apps')).toBeVisible(); + + for (const { name, hash } of demoPages) { + const link = page.locator('a', { hasText: name }).first(); + await expect(link).toBeVisible(); + await expect(link).toHaveAttribute('href', hash); + } + + for (const { name, href } of appLinks) { + const link = page.locator(`a[href="${href}"]`).first(); + await expect(link).toBeVisible(); + await expect(link).toHaveAttribute('href', href); + await expect(link).toContainText(name); + await expect(link).toContainText(href); + } + + await expectAppBar(page, 'Module Federation Demo'); + + const alert = page.locator('.alert'); + await expect(alert).toBeVisible(); + await expect(alert).toHaveText(/Alert from LitElement/); + await expect(page.locator('.closebtn')).toBeVisible(); + + for (const paragraph of mainPageParagraphs) { + await expect(page.locator('p', { hasText: paragraph })).toBeVisible(); + } + + await expect( + page.getByRole('link', { name: 'https://github.com/module-federation/mfe-webpack-demo' }), + ).toHaveAttribute('href', 'https://github.com/module-federation/mfe-webpack-demo'); + + const actionButton = page.locator('action-button button'); + await expect(actionButton).toHaveText('Lit Element Action'); + await expect(actionButton).toHaveCSS('background-color', 'rgb(219, 112, 147)'); + }); + + test('main tab functionality', async ({ page }) => { + await page.goto(base); + + page.once('dialog', async dialog => { + expect(dialog.message()).toBe('You have pressed a button.'); + await dialog.accept(); + }); + + await page.locator('action-button button').click(); + await page.locator('.closebtn').click(); + await expect(page.locator('.alert')).toBeHidden(); + + for (const { name, hash } of demoPages) { + await page.locator('a', { hasText: name }).first().click(); + await expect(page).toHaveURL(`${base}/${hash}`); + } + + await page.locator('a', { hasText: 'Main' }).first().click(); + await expect(page).toHaveURL(`${base}/#/`); + + for (const { href } of appLinks) { + const response = await page.request.get(href); + expect(response.ok()).toBeTruthy(); + } + }); + + test('UI library page renders remote button', async ({ page }) => { + await page.goto(`${base}/#/ui-library`); + + await expectAppBar(page, 'UI Library Demo'); + + for (const paragraph of uiLibraryParagraphs) { + await expect(page.locator('p', { hasText: paragraph })).toBeVisible(); + } + + await expect(page.locator('a[href="http://localhost:3003/"]').first()).toHaveAttribute( + 'href', + 'http://localhost:3003/', + ); + await expect(page.locator('a[href="http://localhost:3001/#/routing/foo"]').first()).toHaveAttribute( + 'href', + 'http://localhost:3001/#/routing/foo', + ); + + const styledButton = page.getByRole('button', { name: '💅 Button' }); + await expect(styledButton).toBeVisible(); + await expect(styledButton).toHaveCSS('background-color', 'rgb(219, 112, 147)'); + }); + + test('dialog page loads and dialog opens', async ({ page }) => { + await page.goto(`${base}/#/dialog`); + + await expectAppBar(page, 'Dialog Demo'); + await expect( + page.locator('p', { + hasText: + 'Clicking the button below will render a Dialog using React Portal. This dialog component is being lazy loaded from the app #2.', + }), + ).toBeVisible(); + + await page.getByRole('button', { name: 'Open Dialog' }).click(); + const dialog = page.locator('[role="dialog"]'); + await expect(dialog.getByRole('heading', { name: 'Dialog Example' })).toBeVisible(); + await expect( + dialog.getByText('This is a dialog from the Material UI app rendered in a React Portal.'), + ).toBeVisible(); + await dialog.getByRole('button', { name: 'Nice' }).click(); + await expect(dialog).not.toBeVisible(); + }); + + test('svelte page updates greeting', async ({ page }) => { + await page.goto(`${base}/#/svelte`); + + await expectAppBar(page, 'Svelte Demo'); + + const input = page.locator('input'); + await expect(input).toBeVisible(); + await input.fill('May The Force Be With You'); + await expect(page.locator('h1')).toHaveText('Hello From Svelte May The Force Be With You!'); + }); + + test('routing page renders tabs', async ({ page }) => { + await page.goto(`${base}/#/routing/foo`); + + await expectAppBar(page, 'Routing Demo'); + + for (const paragraph of routingParagraphs) { + await expect(page.locator('p', { hasText: paragraph })).toBeVisible(); + } + + await expect(page.getByRole('tab', { name: 'Foo' })).toBeVisible(); + await expect(page.getByText('Foo Content')).toBeVisible(); + + await page.getByRole('tab', { name: 'Bar' }).click(); + await expect(page.getByText('Bar Content')).toBeVisible(); + + const barButton = page.getByRole('button', { name: 'Bar Button' }); + await expect(barButton).toBeVisible(); + await expect(barButton).toHaveCSS('background-color', 'rgb(219, 112, 147)'); + }); +}); diff --git a/comprehensive-demo-react18/e2e/checkApp2.spec.ts b/comprehensive-demo-react18/e2e/checkApp2.spec.ts new file mode 100644 index 00000000000..059c44e4573 --- /dev/null +++ b/comprehensive-demo-react18/e2e/checkApp2.spec.ts @@ -0,0 +1,39 @@ +import { test, expect } from '@playwright/test'; + +const base = 'http://localhost:3002'; + +test.describe('Comprehensive Demo App2', () => { + test('renders blocks, dialog and tabs', async ({ page }) => { + await page.goto(base); + + await expect(page.locator('header').first()).toHaveCSS('background-color', 'rgb(63, 81, 181)'); + await expect(page.getByRole('heading', { name: 'Material UI App' })).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Dialog Component' })).toBeVisible(); + + const openDialogButton = page.getByRole('button', { name: 'Open Dialog' }); + await expect(openDialogButton).toBeVisible(); + await openDialogButton.click(); + + const dialog = page.locator('[role="dialog"]'); + await expect(dialog.getByRole('heading', { name: 'Dialog Example' })).toBeVisible(); + await expect( + dialog.getByText('This is a dialog from the Material UI app rendered in a React Portal.'), + ).toBeVisible(); + await dialog.getByRole('button', { name: 'Nice' }).click(); + await expect(dialog).not.toBeVisible(); + + await expect(page.getByRole('heading', { name: 'Tabs Component' })).toBeVisible(); + const fooTab = page.getByRole('tab', { name: 'Foo' }); + const barTab = page.getByRole('tab', { name: 'Bar' }); + await expect(fooTab).toBeVisible(); + await expect(barTab).toBeVisible(); + await expect(page.getByText('Foo Content')).toBeVisible(); + + await barTab.click(); + await expect(page.getByText('Bar Content')).toBeVisible(); + await expect(page.getByRole('button', { name: 'Bar Button' })).toHaveCSS( + 'background-color', + 'rgb(219, 112, 147)', + ); + }); +}); diff --git a/comprehensive-demo-react18/e2e/checkApp3.spec.ts b/comprehensive-demo-react18/e2e/checkApp3.spec.ts new file mode 100644 index 00000000000..fb19a7ad376 --- /dev/null +++ b/comprehensive-demo-react18/e2e/checkApp3.spec.ts @@ -0,0 +1,16 @@ +import { test, expect } from '@playwright/test'; + +const base = 'http://localhost:3003'; + +test.describe('Comprehensive Demo App3', () => { + test('shows styled button', async ({ page }) => { + await page.goto(base); + + await expect(page.locator('header').first()).toHaveCSS('background-color', 'rgb(63, 81, 181)'); + await expect(page.getByRole('heading', { name: 'Styled Components App' })).toBeVisible(); + + const button = page.getByRole('button', { name: '💅 Test Button' }); + await expect(button).toBeVisible(); + await expect(button).toHaveCSS('background-color', 'rgb(219, 112, 147)'); + }); +}); diff --git a/comprehensive-demo-react18/e2e/checkApp4.spec.ts b/comprehensive-demo-react18/e2e/checkApp4.spec.ts new file mode 100644 index 00000000000..156c3808b70 --- /dev/null +++ b/comprehensive-demo-react18/e2e/checkApp4.spec.ts @@ -0,0 +1,8 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Comprehensive Demo App4', () => { + test('shows svelte greeting', async ({ page }) => { + await page.goto('http://localhost:3004'); + await expect(page.locator('h1')).toHaveText('Hello From Svelte world!'); + }); +}); diff --git a/comprehensive-demo-react18/e2e/checkApp5.spec.ts b/comprehensive-demo-react18/e2e/checkApp5.spec.ts new file mode 100644 index 00000000000..707dc8e281f --- /dev/null +++ b/comprehensive-demo-react18/e2e/checkApp5.spec.ts @@ -0,0 +1,27 @@ +import { test, expect } from '@playwright/test'; + +const base = 'http://localhost:3005'; + +test.describe('Comprehensive Demo App5', () => { + test('shows button and alert', async ({ page }) => { + await page.goto(base); + + const button = page.locator('action-button').locator('button'); + await expect(button).toHaveText('bar'); + await expect(page.locator('.alert')).toHaveText(/Hello/); + await expect(page.locator('.closebtn')).toBeVisible(); + }); + + test('button triggers alert and close hides it', async ({ page }) => { + await page.goto(base); + + page.once('dialog', async dialog => { + expect(dialog.message()).toBe('You have pressed a button.'); + await dialog.accept(); + }); + + await page.locator('action-button').locator('button').click(); + await page.locator('.closebtn').click(); + await expect(page.locator('.alert')).toBeHidden(); + }); +}); diff --git a/comprehensive-demo-react18/e2e/checkApplications.spec.ts b/comprehensive-demo-react18/e2e/checkApplications.spec.ts new file mode 100644 index 00000000000..17caba324ed --- /dev/null +++ b/comprehensive-demo-react18/e2e/checkApplications.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +const apps = [ + { port: 3001, name: 'App 1', selector: 'h6', text: 'Module Federation Demo' }, + { port: 3002, name: 'App 2', selector: 'h6', text: 'Material UI App' }, + { port: 3003, name: 'App 3', selector: 'h6', text: 'Styled Components App' }, + { port: 3004, name: 'App 4', selector: 'h1', text: 'Hello From Svelte world!' }, + { port: 3005, name: 'App 5', selector: 'action-button button', text: 'bar' }, +]; + +apps.forEach(({ port, name, selector, text }) => { + test.describe(name, () => { + test(`build and run ${name}`, async ({ page }) => { + await page.goto(`http://localhost:${port}`); + await expect(page.locator(selector, { hasText: text })).toBeVisible(); + }); + }); +}); diff --git a/comprehensive-demo-react18/e2e/checkComrehensiveDemoReact18App1.cy.ts b/comprehensive-demo-react18/e2e/checkComrehensiveDemoReact18App1.cy.ts deleted file mode 100644 index 05ebf8ba48b..00000000000 --- a/comprehensive-demo-react18/e2e/checkComrehensiveDemoReact18App1.cy.ts +++ /dev/null @@ -1,328 +0,0 @@ -import { Constants } from '../../cypress-e2e/fixtures/constants'; -import { baseSelectors, selectors } from '../../cypress-e2e/common/selectors'; -import { BaseMethods } from '../../cypress-e2e/common/base'; -import { CssAttr } from '../../cypress-e2e/types/cssAttr'; - -const basePage: BaseMethods = new BaseMethods(); - -describe('Comprehencive Demo React 18', () => { - context('Check is Comprehensive Demo App1 working and have elements', () => { - beforeEach(() => { - basePage.openLocalhost({ - number: 3001, - }); - }); - - it('Check App build and running & Check app elements exist', () => { - basePage.checkElementVisibility({ - selector: selectors.comprehensiveDemoApp.blockSelectors.sideBarBlock, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.headers.h6, - text: Constants.elementsText.comprehensiveDemoApp.App1.sideNavHeaderText, - }); - Constants.elementsText.comprehensiveDemoApp.comprehensiveDemoDemoPages.forEach(demoPage => { - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.spans.span, - text: demoPage.name, - }); - basePage.checkElementHaveProperty({ - selector: baseSelectors.tags.coreElements.link, - index: demoPage.index, - attr: Constants.commonConstantsData.commonAttributes.attr, - prop: Constants.commonConstantsData.commonAttributes.href, - value: demoPage.link, - }); - }); - Constants.elementsText.comprehensiveDemoApp.comprehensiveDemoDemoPages.forEach( - application => { - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.spans.span, - text: application.name, - }); - basePage.checkElementHaveProperty({ - selector: baseSelectors.tags.coreElements.link, - index: application.index, - attr: Constants.commonConstantsData.commonAttributes.attr, - prop: Constants.commonConstantsData.commonAttributes.href, - value: application.link, - }); - }, - ); - basePage.checkElementVisibility({ - selector: baseSelectors.tags.headers.header, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.headers.h6, - text: Constants.elementsText.comprehensiveDemoApp.App1.mainPage.headerText, - }); - basePage.checkElementHaveProperty({ - selector: baseSelectors.tags.headers.header, - prop: CssAttr.backgroundColor, - value: Constants.color.oceanBluePearl, - }); - basePage.checkElementVisibility({ - selector: selectors.comprehensiveDemoApp.alert, - }); - basePage.checkElementWithTextPresence({ - selector: selectors.comprehensiveDemoApp.alert, - text: Constants.elementsText.comprehensiveDemoApp.App1.mainPage.alertMessage, - }); - basePage.checkElementVisibility({ - selector: selectors.comprehensiveDemoApp.closeButton, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: Constants.elementsText.comprehensiveDemoApp.App1.mainPage.paragraphs.first, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: Constants.elementsText.comprehensiveDemoApp.App1.mainPage.paragraphs.second, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: Constants.elementsText.comprehensiveDemoApp.App1.mainPage.paragraphs.third, - }); - basePage.checkElementHaveProperty({ - selector: baseSelectors.tags.coreElements.link, - index: Constants.commonConstantsData.commonIndexes.ten, - attr: Constants.commonConstantsData.commonAttributes.attr, - prop: Constants.commonConstantsData.commonAttributes.href, - value: Constants.hrefs.comprehensiveDemoApp.gitHub, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App1.mainPage.buttonText, - }); - basePage.checkElementHaveProperty({ - selector: baseSelectors.tags.coreElements.button, - prop: CssAttr.backgroundColor, - value: Constants.color.paleVioletRed, - }); - }); - - it('Check Main Tab Functionality', () => { - basePage.checkBrowserAlertByText({ - selector: baseSelectors.tags.coreElements.button, - alertMessage: Constants.elementsText.comprehensiveDemoApp.alertMessage, - }); - basePage.clickElementBySelector({ - selector: selectors.comprehensiveDemoApp.closeButton, - }); - basePage.checkElementHaveProperty({ - selector: selectors.comprehensiveDemoApp.alert, - attr: Constants.commonConstantsData.commonAttributes.attr, - prop: Constants.commonConstantsData.commonAttributes.style, - value: Constants.commonConstantsData.commonAttributes.displayNone, - }); - Constants.elementsText.comprehensiveDemoApp.comprehensiveDemoDemoPages.forEach(demoPage => { - basePage.clickElementWithText({ - selector: baseSelectors.tags.coreElements.link, - text: demoPage.name, - }); - basePage.checkUrlText(demoPage.link, true); - }); - Constants.elementsText.comprehensiveDemoApp.comprehensiveDemoDemoPages.forEach( - application => { - basePage.clickElementWithText({ - selector: baseSelectors.tags.coreElements.link, - text: application.name, - }); - basePage.checkUrlText(application.link, true); - basePage.goBack(); - }, - ); - }); - - it('Check UI Library elements', () => { - basePage.openLocalhost({ - number: 3001, - path: Constants.hrefs.comprehensiveDemoApp.uiLibrary, - }); - basePage.checkElementVisibility({ - selector: baseSelectors.tags.headers.header, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.headers.h6, - text: Constants.elementsText.comprehensiveDemoApp.App1.uiLibrary.headerText, - }); - basePage.checkElementHaveProperty({ - selector: baseSelectors.tags.headers.header, - prop: CssAttr.backgroundColor, - value: Constants.color.oceanBluePearl, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: Constants.elementsText.comprehensiveDemoApp.App1.uiLibrary.paragraphs.first, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: Constants.elementsText.comprehensiveDemoApp.App1.uiLibrary.paragraphs.second, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: Constants.elementsText.comprehensiveDemoApp.App1.uiLibrary.paragraphs.third, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.commonConstantsData.commonButtonWithEmoji, - }); - basePage.checkElementHaveProperty({ - selector: baseSelectors.tags.coreElements.link, - index: Constants.commonConstantsData.commonIndexes.ten, - attr: Constants.commonConstantsData.commonAttributes.attr, - prop: Constants.commonConstantsData.commonAttributes.href, - value: Constants.hrefs.comprehensiveDemoApp.app3, - }); - basePage.checkElementHaveProperty({ - selector: baseSelectors.tags.coreElements.link, - index: Constants.commonConstantsData.commonIndexes.eleven, - attr: Constants.commonConstantsData.commonAttributes.attr, - prop: Constants.commonConstantsData.commonAttributes.href, - value: Constants.hrefs.comprehensiveDemoApp.routingDemo, - }); - }); - - it('Check Dialog elements', () => { - basePage.openLocalhost({ - number: 3001, - path: Constants.hrefs.comprehensiveDemoApp.demoDialog, - }); - basePage.checkElementVisibility({ - selector: baseSelectors.tags.headers.header, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.headers.h6, - text: Constants.elementsText.comprehensiveDemoApp.App1.uiLibrary.dialogHeader, - }); - basePage.checkElementHaveProperty({ - selector: baseSelectors.tags.headers.header, - prop: CssAttr.backgroundColor, - value: Constants.color.oceanBluePearl, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: Constants.elementsText.comprehensiveDemoApp.App1.uiLibrary.dialogParagraph, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App2.openDialogButtonText, - }); - basePage.clickElementWithText({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App2.openDialogButtonText, - }); - basePage.checkElementVisibility({ - selector: selectors.comprehensiveDemoApp.app2Dialog, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.headers.h2, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogHeader, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogParagraph, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogButtonText, - }); - basePage.clickElementWithText({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogButtonText, - }); - }); - - it('Check Svelte Page elements', () => { - basePage.openLocalhost({ - number: 3001, - path: Constants.hrefs.comprehensiveDemoApp.demoSvelte, - }); - basePage.checkElementVisibility({ - selector: baseSelectors.tags.headers.header, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.headers.h6, - text: Constants.elementsText.comprehensiveDemoApp.App1.svelte.headerText, - }); - basePage.checkElementVisibility({ - selector: baseSelectors.tags.inputs.input, - }); - basePage.fillField({ - selector: baseSelectors.tags.inputs.input, - text: Constants.commonConstantsData.standardPhrase, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.headers.h1, - text: Constants.updatedConstantsData.baseSvelteIntroMessage, - }); - }); - - it('Check Routing elements', () => { - basePage.openLocalhost({ - number: 3001, - path: Constants.hrefs.comprehensiveDemoApp.routingDemo.replace( - 'http://localhost:3001/', - '', - ), - }); - basePage.checkElementVisibility({ - selector: baseSelectors.tags.headers.header, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.headers.h6, - text: Constants.elementsText.comprehensiveDemoApp.App1.routing.headerText, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: Constants.elementsText.comprehensiveDemoApp.App1.routing.paragraphs.first, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: Constants.elementsText.comprehensiveDemoApp.App1.routing.paragraphs.second, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: Constants.elementsText.comprehensiveDemoApp.App1.routing.paragraphs.third, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: Constants.elementsText.comprehensiveDemoApp.App1.routing.paragraphs.forth, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: Constants.elementsText.comprehensiveDemoApp.App1.routing.paragraphs.forth, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogTabs.firstTab.name, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogTabs.firstTab.name, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.div, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogTabs.firstTab.paragraphText, - }); - basePage.clickElementWithText({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogTabs.secondTab.name, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.div, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogTabs.secondTab.paragraphText, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogTabs.secondTab.buttonText, - }); - basePage.checkElementHaveProperty({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogTabs.secondTab.buttonText, - prop: CssAttr.backgroundColor, - value: Constants.color.paleVioletRed, - }); - }); - }); -}); diff --git a/comprehensive-demo-react18/e2e/checkComrehensiveDemoReact18App2.cy.ts b/comprehensive-demo-react18/e2e/checkComrehensiveDemoReact18App2.cy.ts deleted file mode 100644 index 0fc24d62ef9..00000000000 --- a/comprehensive-demo-react18/e2e/checkComrehensiveDemoReact18App2.cy.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { selectors } from '../../cypress-e2e/common/selectors'; -import { BaseMethods } from '../../cypress-e2e/common/base'; -import { baseSelectors } from '../../cypress-e2e/common/selectors'; -import { Constants } from '../../cypress-e2e/fixtures/constants'; -import { CssAttr } from '../../cypress-e2e/types/cssAttr'; - -const basePage: BaseMethods = new BaseMethods(); - -describe('Comprehencive Demo React 18', () => { - context('Check is Comprehensive Demo App2 working and have elements', () => { - beforeEach(() => { - basePage.openLocalhost({ - number: 3002, - }); - }); - - it('Check App build and running & Check app elements exist', () => { - basePage.checkElementVisibility({ - selector: selectors.comprehensiveDemoApp.blockSelectors.firstBlock, - }); - basePage.checkElementVisibility({ - selector: baseSelectors.tags.headers.header, - }); - basePage.checkElementHaveProperty({ - selector: baseSelectors.tags.headers.header, - prop: CssAttr.backgroundColor, - value: Constants.color.oceanBluePearl, - }); - basePage.checkElementVisibility({ - selector: selectors.comprehensiveDemoApp.blockSelectors.secondBlock, - }); - basePage.checkElementHaveProperty({ - selector: selectors.comprehensiveDemoApp.blockSelectors.secondBlock, - prop: CssAttr.backgroundColor, - value: Constants.color.alabaster, - }); - basePage.checkElementVisibility({ - selector: selectors.comprehensiveDemoApp.blockSelectors.thirdBlock, - }); - basePage.checkElementHaveProperty({ - selector: selectors.comprehensiveDemoApp.blockSelectors.thirdBlock, - prop: CssAttr.backgroundColor, - value: Constants.color.white, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.headers.h6, - text: Constants.elementsText.comprehensiveDemoApp.App2.headerText, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.headers.h6, - text: Constants.elementsText.comprehensiveDemoApp.App2.paragraphText, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App2.openDialogButtonText, - }); - basePage.clickElementWithText({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App2.openDialogButtonText, - }); - basePage.checkElementVisibility({ - selector: selectors.comprehensiveDemoApp.app2Dialog, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.headers.h2, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogHeader, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogParagraph, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogButtonText, - }); - basePage.clickElementWithText({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogButtonText, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.headers.h6, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogTabs.headerText, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogTabs.firstTab.name, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.div, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogTabs.firstTab.paragraphText, - }); - basePage.clickElementWithText({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogTabs.secondTab.name, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogTabs.secondTab.name, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.div, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogTabs.secondTab.paragraphText, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogTabs.secondTab.buttonText, - }); - basePage.checkElementHaveProperty({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App2.dialogTabs.secondTab.buttonText, - prop: CssAttr.backgroundColor, - value: Constants.color.paleVioletRed, - }); - }); - }); -}); diff --git a/comprehensive-demo-react18/e2e/checkComrehensiveDemoReact18App3.cy.ts b/comprehensive-demo-react18/e2e/checkComrehensiveDemoReact18App3.cy.ts deleted file mode 100644 index 3c481e20eb0..00000000000 --- a/comprehensive-demo-react18/e2e/checkComrehensiveDemoReact18App3.cy.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Constants } from '../../cypress-e2e/fixtures/constants'; -import { baseSelectors, selectors } from '../../cypress-e2e/common/selectors'; -import { BaseMethods } from '../../cypress-e2e/common/base'; -import { CssAttr } from '../../cypress-e2e/types/cssAttr'; - -const basePage: BaseMethods = new BaseMethods(); - -describe('Comprehencive Demo React 18', () => { - context('Check is Comprehensive Demo App3 working and have elements', () => { - beforeEach(() => { - basePage.openLocalhost({ - number: 3003, - }); - }); - - it('Check App build and running & Check app elements exist', () => { - basePage.checkElementVisibility({ - selector: selectors.comprehensiveDemoApp.blockSelectors.firstBlock, - }); - basePage.checkElementVisibility({ - selector: baseSelectors.tags.headers.header, - }); - basePage.checkElementHaveProperty({ - selector: baseSelectors.tags.headers.header, - prop: CssAttr.backgroundColor, - value: Constants.color.oceanBluePearl, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.headers.h6, - text: Constants.elementsText.comprehensiveDemoApp.App3.headerText, - }); - basePage.checkElementVisibility({ - selector: selectors.comprehensiveDemoApp.blockSelectors.secondBlock, - }); - basePage.checkElementHaveProperty({ - selector: selectors.comprehensiveDemoApp.blockSelectors.secondBlock, - prop: CssAttr.backgroundColor, - value: Constants.color.alabaster, - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.commonConstantsData.commonButtonWithEmoji.replace( - Constants.commonConstantsData.button, - Constants.elementsText.comprehensiveDemoApp.App3.buttonText, - ), - }); - basePage.checkElementHaveProperty({ - selector: baseSelectors.tags.coreElements.button, - prop: CssAttr.backgroundColor, - value: Constants.color.paleVioletRed, - }); - }); - }); -}); diff --git a/comprehensive-demo-react18/e2e/checkComrehensiveDemoReact18App4.cy.ts b/comprehensive-demo-react18/e2e/checkComrehensiveDemoReact18App4.cy.ts deleted file mode 100644 index fdb2b4b78eb..00000000000 --- a/comprehensive-demo-react18/e2e/checkComrehensiveDemoReact18App4.cy.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Constants } from '../../cypress-e2e/fixtures/constants'; -import { baseSelectors } from '../../cypress-e2e/common/selectors'; -import { BaseMethods } from '../../cypress-e2e/common/base'; - -const basePage: BaseMethods = new BaseMethods(); - -describe('Comprehencive Demo React 18', () => { - context('Check is Comprehensive Demo App4 working and have elements', () => { - beforeEach(() => { - basePage.openLocalhost({ - number: 3004, - }); - }); - - it('Check App build and running & Check app elements exist', () => { - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.headers.h1, - text: Constants.elementsText.comprehensiveDemoApp.App4.headerText, - }); - }); - }); -}); diff --git a/comprehensive-demo-react18/e2e/checkComrehensiveDemoReact18App5.cy.ts b/comprehensive-demo-react18/e2e/checkComrehensiveDemoReact18App5.cy.ts deleted file mode 100644 index 7e16ffd5e01..00000000000 --- a/comprehensive-demo-react18/e2e/checkComrehensiveDemoReact18App5.cy.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Constants } from '../../cypress-e2e/fixtures/constants'; -import { baseSelectors, selectors } from '../../cypress-e2e/common/selectors'; -import { BaseMethods } from '../../cypress-e2e/common/base'; - -const basePage: BaseMethods = new BaseMethods(); - -describe('Comprehencive Demo React 18', () => { - context('Check is Comprehensive Demo App5 working and have elements', () => { - beforeEach(() => { - basePage.openLocalhost({ - number: 3005, - }); - }); - - it('Check App build and running & Check app elements exist', () => { - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.coreElements.button, - text: Constants.elementsText.comprehensiveDemoApp.App5.buttonText, - }); - basePage.checkElementVisibility({ - selector: selectors.comprehensiveDemoApp.alert, - }); - basePage.checkElementWithTextPresence({ - selector: selectors.comprehensiveDemoApp.alert, - text: Constants.elementsText.comprehensiveDemoApp.App5.alertText, - }); - basePage.checkElementVisibility({ - selector: selectors.comprehensiveDemoApp.closeButton, - }); - }); - - it('Check Application functionality (Alert message & close button exist)', () => { - basePage.checkBrowserAlertByText({ - selector: baseSelectors.tags.coreElements.button, - alertMessage: Constants.elementsText.comprehensiveDemoApp.alertMessage, - }); - basePage.clickElementBySelector({ - selector: selectors.comprehensiveDemoApp.closeButton, - }); - basePage.checkElementHaveProperty({ - selector: selectors.comprehensiveDemoApp.alert, - attr: Constants.commonConstantsData.commonAttributes.attr, - prop: Constants.commonConstantsData.commonAttributes.style, - value: Constants.commonConstantsData.commonAttributes.displayNone, - }); - }); - }); -}); diff --git a/comprehensive-demo-react18/e2e/runAllComprehensiveDemoReact18Tests.cy.ts b/comprehensive-demo-react18/e2e/runAllComprehensiveDemoReact18Tests.cy.ts deleted file mode 100644 index 29469f47c24..00000000000 --- a/comprehensive-demo-react18/e2e/runAllComprehensiveDemoReact18Tests.cy.ts +++ /dev/null @@ -1,5 +0,0 @@ -import './checkComrehensiveDemoReact18App1.cy'; -import './checkComrehensiveDemoReact18App2.cy'; -import './checkComrehensiveDemoReact18App3.cy'; -import './checkComrehensiveDemoReact18App4.cy'; -import './checkComrehensiveDemoReact18App5.cy'; diff --git a/comprehensive-demo-react18/package.json b/comprehensive-demo-react18/package.json index 4a500446d51..07df0696566 100644 --- a/comprehensive-demo-react18/package.json +++ b/comprehensive-demo-react18/package.json @@ -5,19 +5,24 @@ "scripts": { "start": "pnpm build && pnpm serve", "legacy:start": "pnpm legacy:build && pnpm serve", - "build": "time pnpm --filter comprehensive-demo-react18_app* --parallel build", - "legacy:build": "time pnpm --filter comprehensive-demo-react18_app* legacy:build", + "build": "pnpm --filter comprehensive-demo-react18_app* --parallel build", + "legacy:build": "pnpm --filter comprehensive-demo-react18_app* legacy:build", "serve": "pnpm --filter comprehensive-demo-react18_app* --parallel serve", "dev": "pnpm --filter comprehensive-demo-react18_app* --parallel dev", "legacy:dev": "pnpm --filter comprehensive-demo-react18_app* --parallel legacy:dev", "clean": "pnpm --filter comprehensive-demo-react18_app* --parallel clean", - "e2e:ci": "pnpm start & wait-on http-get://localhost:3001/ && npx cypress run --config-file ../cypress-e2e/config/cypress.config.ts --config '{\"supportFile\": \"../cypress-e2e/support/e2e.ts\"}' --spec \"./e2e/*.cy.ts\" --browser=chrome", - "legacy:e2e:ci": "pnpm legacy:start & wait-on http-get://localhost:3001/ && npx cypress run --config-file ../cypress-e2e/config/cypress.config.ts --config '{\"supportFile\": \"../cypress-e2e/support/e2e.ts\"}' --spec \"./e2e/*.cy.ts\" --browser=chrome" + "test:e2e": "npx playwright test", + "test:e2e:ui": "npx playwright test --ui", + "test:e2e:debug": "npx playwright test --debug", + "e2e:ci": "pnpm build && npx playwright install --with-deps && npx playwright test --reporter=list", + "legacy:e2e:ci": "pnpm legacy:build && npx playwright install --with-deps && LEGACY_START=true npx playwright test --reporter=list" }, "devDependencies": { + "@playwright/test": "^1.54.2", "@rsdoctor/rspack-plugin": "0.3.7", "@rsdoctor/webpack-plugin": "0.3.7", "concurrently": "8.2.2", + "playwright": "^1.54.2", "wait-on": "7.2.0" } } diff --git a/comprehensive-demo-react18/playwright.config.ts b/comprehensive-demo-react18/playwright.config.ts new file mode 100644 index 00000000000..91b4184c4f6 --- /dev/null +++ b/comprehensive-demo-react18/playwright.config.ts @@ -0,0 +1,40 @@ +import { defineConfig, devices } from '@playwright/test'; + +const useLegacyStart = !!process.env.LEGACY_START; + +export default defineConfig({ + testDir: './e2e', + timeout: 60000, + expect: { + timeout: 15000, + }, + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 1 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: [ + ['html', { outputFolder: 'playwright-report', open: 'never' }], + ['list'], + ], + use: { + baseURL: 'http://localhost:3001', + trace: 'on-first-retry', + screenshot: 'only-on-failure', + video: 'retain-on-failure', + viewport: { width: 1920, height: 1080 }, + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: useLegacyStart ? 'pnpm legacy:start' : 'pnpm start', + port: 3001, + reuseExistingServer: !process.env.CI, + timeout: 120000, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8c2e31dbbc5..b5a3fb9ba5d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -893,7 +893,7 @@ importers: dependencies: '@modern-js/runtime': specifier: 2.54.6 - version: 2.54.6(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(webpack@5.101.0(@swc/core@1.11.31(@swc/helpers@0.5.17))) + version: 2.54.6(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(webpack@5.101.0(@swc/core@1.11.31(@swc/helpers@0.5.3))(esbuild@0.17.19)) react: specifier: ~18.3.0 version: 18.3.1 @@ -903,22 +903,22 @@ importers: devDependencies: '@modern-js-app/eslint-config': specifier: 2.54.6 - version: 2.54.6(@swc/helpers@0.5.17)(typescript@5.6.2) + version: 2.54.6(@swc/helpers@0.5.3)(typescript@5.6.2) '@modern-js/app-tools': specifier: 2.54.6 - version: 2.54.6(@rspack/core@1.4.11(@swc/helpers@0.5.17))(@swc/core@1.11.31(@swc/helpers@0.5.17))(@types/webpack@5.28.0(@swc/core@1.11.31(@swc/helpers@0.5.17)))(encoding@0.1.13)(eslint@9.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@5.3.11(@babel/core@7.28.3)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))(ts-node@10.9.2(@swc/core@1.11.31(@swc/helpers@0.5.17))(@types/node@20.16.5)(typescript@5.6.2))(tsconfig-paths@4.2.0)(type-fest@2.19.0)(typescript@5.6.2)(webpack-dev-server@4.15.2(webpack@5.101.0(@swc/core@1.11.31(@swc/helpers@0.5.17))))(webpack-hot-middleware@2.26.1) + version: 2.54.6(@rspack/core@0.4.5)(@swc/core@1.11.31(@swc/helpers@0.5.3))(@types/webpack@5.28.0(@swc/core@1.11.31(@swc/helpers@0.5.3))(esbuild@0.17.19))(encoding@0.1.13)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@5.3.11(@babel/core@7.24.7)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))(ts-node@10.9.2(@swc/core@1.11.31(@swc/helpers@0.5.3))(@types/node@20.16.5)(typescript@5.6.2))(tsconfig-paths@4.2.0)(type-fest@2.19.0)(typescript@5.6.2)(webpack-dev-server@4.15.2(webpack@5.101.0(@swc/core@1.11.31(@swc/helpers@0.5.3))(esbuild@0.17.19)))(webpack-hot-middleware@2.26.1) '@modern-js/builder-rspack-provider': specifier: 2.46.1 - version: 2.46.1(@babel/traverse@7.28.3)(@rsbuild/core@1.4.12)(@swc/core@1.11.31(@swc/helpers@0.5.17))(@types/express@4.17.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(ts-node@10.9.2(@swc/core@1.11.31(@swc/helpers@0.5.17))(@types/node@20.16.5)(typescript@5.6.2))(tsconfig-paths@4.2.0)(typescript@5.6.2) + version: 2.46.1(@babel/traverse@7.28.3)(@rsbuild/core@0.7.10)(@swc/core@1.11.31(@swc/helpers@0.5.3))(@types/express@4.17.21)(esbuild@0.17.19)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(ts-node@10.9.2(@swc/core@1.11.31(@swc/helpers@0.5.3))(@types/node@20.16.5)(typescript@5.6.2))(tsconfig-paths@4.2.0)(typescript@5.6.2) '@modern-js/eslint-config': specifier: 2.54.6 - version: 2.54.6(@swc/helpers@0.5.17)(typescript@5.6.2) + version: 2.54.6(@swc/helpers@0.5.3)(typescript@5.6.2) '@modern-js/tsconfig': specifier: 2.54.6 version: 2.54.6 '@module-federation/enhanced': specifier: 0.17.1 - version: 0.17.1(@rspack/core@1.4.11(@swc/helpers@0.5.17))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2)(webpack@5.101.0(@swc/core@1.11.31(@swc/helpers@0.5.17))) + version: 0.17.1(@rspack/core@0.4.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2)(webpack@5.101.0(@swc/core@1.11.31(@swc/helpers@0.5.3))(esbuild@0.17.19)) husky: specifier: 9.0.11 version: 9.0.11 @@ -936,7 +936,7 @@ importers: dependencies: '@modern-js/runtime': specifier: 2.54.6 - version: 2.54.6(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(webpack@5.101.0(@swc/core@1.11.31(@swc/helpers@0.5.3))(esbuild@0.17.19)) + version: 2.54.6(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(webpack@5.101.0(@swc/core@1.11.31(@swc/helpers@0.5.17))) react: specifier: ~18.3.0 version: 18.3.1 @@ -946,22 +946,22 @@ importers: devDependencies: '@modern-js-app/eslint-config': specifier: 2.54.6 - version: 2.54.6(@swc/helpers@0.5.3)(typescript@5.6.2) + version: 2.54.6(@swc/helpers@0.5.17)(typescript@5.6.2) '@modern-js/app-tools': specifier: 2.54.6 - version: 2.54.6(@rspack/core@0.4.5)(@swc/core@1.11.31(@swc/helpers@0.5.3))(@types/webpack@5.28.0(@swc/core@1.11.31(@swc/helpers@0.5.3))(esbuild@0.17.19))(encoding@0.1.13)(eslint@8.57.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@5.3.11(@babel/core@7.24.7)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))(ts-node@10.9.2(@swc/core@1.11.31(@swc/helpers@0.5.3))(@types/node@20.16.5)(typescript@5.6.2))(tsconfig-paths@4.2.0)(type-fest@2.19.0)(typescript@5.6.2)(webpack-dev-server@4.15.2(webpack@5.101.0(@swc/core@1.11.31(@swc/helpers@0.5.3))(esbuild@0.17.19)))(webpack-hot-middleware@2.26.1) + version: 2.54.6(@rspack/core@1.4.11(@swc/helpers@0.5.17))(@swc/core@1.11.31(@swc/helpers@0.5.17))(@types/webpack@5.28.0(@swc/core@1.11.31(@swc/helpers@0.5.17)))(encoding@0.1.13)(eslint@9.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@5.3.11(@babel/core@7.28.3)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))(ts-node@10.9.2(@swc/core@1.11.31(@swc/helpers@0.5.17))(@types/node@20.16.5)(typescript@5.6.2))(tsconfig-paths@4.2.0)(type-fest@2.19.0)(typescript@5.6.2)(webpack-dev-server@4.15.2(webpack@5.101.0(@swc/core@1.11.31(@swc/helpers@0.5.17))))(webpack-hot-middleware@2.26.1) '@modern-js/builder-rspack-provider': specifier: 2.46.1 - version: 2.46.1(@babel/traverse@7.28.3)(@rsbuild/core@0.7.10)(@swc/core@1.11.31(@swc/helpers@0.5.3))(@types/express@4.17.21)(esbuild@0.17.19)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(ts-node@10.9.2(@swc/core@1.11.31(@swc/helpers@0.5.3))(@types/node@20.16.5)(typescript@5.6.2))(tsconfig-paths@4.2.0)(typescript@5.6.2) + version: 2.46.1(@babel/traverse@7.28.3)(@rsbuild/core@1.4.12)(@swc/core@1.11.31(@swc/helpers@0.5.17))(@types/express@4.17.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(ts-node@10.9.2(@swc/core@1.11.31(@swc/helpers@0.5.17))(@types/node@20.16.5)(typescript@5.6.2))(tsconfig-paths@4.2.0)(typescript@5.6.2) '@modern-js/eslint-config': specifier: 2.54.6 - version: 2.54.6(@swc/helpers@0.5.3)(typescript@5.6.2) + version: 2.54.6(@swc/helpers@0.5.17)(typescript@5.6.2) '@modern-js/tsconfig': specifier: 2.54.6 version: 2.54.6 '@module-federation/enhanced': specifier: 0.17.1 - version: 0.17.1(@rspack/core@0.4.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2)(webpack@5.101.0(@swc/core@1.11.31(@swc/helpers@0.5.3))(esbuild@0.17.19)) + version: 0.17.1(@rspack/core@1.4.11(@swc/helpers@0.5.17))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.2)(webpack@5.101.0(@swc/core@1.11.31(@swc/helpers@0.5.17))) husky: specifier: 9.0.11 version: 9.0.11 @@ -1592,6 +1592,9 @@ importers: comprehensive-demo-react18: devDependencies: + '@playwright/test': + specifier: ^1.54.2 + version: 1.54.2 '@rsdoctor/rspack-plugin': specifier: 0.3.7 version: 0.3.7(@rspack/core@1.4.11(@swc/helpers@0.5.17))(@swc/core@1.11.31(@swc/helpers@0.5.17)) @@ -1601,6 +1604,9 @@ importers: concurrently: specifier: 8.2.2 version: 8.2.2 + playwright: + specifier: ^1.54.2 + version: 1.54.2 wait-on: specifier: 7.2.0 version: 7.2.0