diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb6bf71e..965eda38 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: - run: yarn run lint - run: yarn run tsc - run: yarn run build - - run: yarn run test:all + - run: yarn run test:e2e env: CI: true PROGRESS: none diff --git a/Dockerfile b/Dockerfile index 35380f48..f828e839 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ RUN yarn COPY ./ ./ -RUN npm run test:all +RUN npm run test:e2e RUN npm run fetch:blocks diff --git a/Dockerfile.hub b/Dockerfile.hub index 58bbcbc7..94534d3e 100644 --- a/Dockerfile.hub +++ b/Dockerfile.hub @@ -7,7 +7,7 @@ RUN yarn COPY ./ ./ -RUN npm run test:all +RUN npm run test:e2e RUN npm run fetch:blocks diff --git a/jest.config.js b/jest.config.js index 09a2211b..47295738 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,6 +1,5 @@ module.exports = { testURL: 'http://localhost:8000', - testEnvironment: './tests/PuppeteerEnvironment', verbose: false, extraSetupFiles: ['./tests/setupTests.js'], globals: { diff --git a/package.json b/package.json index f74853cf..9e541ee2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ant-design-pro", - "version": "5.0.0", + "version": "5.1.0", "private": true, "description": "An out-of-box UI solution for enterprise applications", "scripts": { @@ -26,19 +26,19 @@ "lint:prettier": "prettier -c --write \"src/**/*\" --end-of-line auto", "lint:style": "stylelint --fix \"src/**/*.less\" --syntax less", "openapi": "umi openapi", + "playwright": "playwright install && playwright test", "precommit": "lint-staged", "prettier": "prettier -c --write \"src/**/*\"", + "serve": "umi-serve", "start": "cross-env UMI_ENV=dev umi dev", "start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev umi dev", "start:no-mock": "cross-env MOCK=none UMI_ENV=dev umi dev", "start:no-ui": "cross-env UMI_UI=none UMI_ENV=dev umi dev", "start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev umi dev", "start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev umi dev", - "pretest": "node ./tests/beforeTest", "test": "umi test", - "test:all": "node ./tests/run-tests.js", + "test:e2e": "node ./tests/run-tests.js", "test:component": "umi test ./src/components", - "serve": "umi-serve", "tsc": "tsc --noEmit" }, "lint-staged": { @@ -76,6 +76,7 @@ }, "devDependencies": { "@ant-design/pro-cli": "^2.0.2", + "@playwright/test": "^1.16.3", "@types/express": "^4.17.0", "@types/history": "^4.7.2", "@types/jest": "^26.0.0", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..ec1b31db --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,22 @@ +// playwright.config.ts +import type { PlaywrightTestConfig } from '@playwright/test'; +import { devices } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + use: { + trace: 'on-first-retry', + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + ], +}; +export default config; diff --git a/src/e2e/baseLayout.e2e.js b/src/e2e/baseLayout.e2e.js deleted file mode 100644 index 459391cd..00000000 --- a/src/e2e/baseLayout.e2e.js +++ /dev/null @@ -1,61 +0,0 @@ -const { uniq } = require('lodash'); -const RouterConfig = require('../../config/config').default.routes; - -const BASE_URL = `http://localhost:${process.env.PORT || 8001}`; - -function formatter(routes, parentPath = '') { - const fixedParentPath = parentPath.replace(/\/{1,}/g, '/'); - let result = []; - routes.forEach((item) => { - if (item.path && !item.path.startsWith('/')) { - result.push(`${fixedParentPath}/${item.path}`.replace(/\/{1,}/g, '/')); - } - if (item.path && item.path.startsWith('/')) { - result.push(`${item.path}`.replace(/\/{1,}/g, '/')); - } - if (item.routes) { - result = result.concat( - formatter(item.routes, item.path ? `${fixedParentPath}/${item.path}` : parentPath), - ); - } - }); - return uniq(result.filter((item) => !!item)); -} - -beforeEach(async () => { - await page.goto(`${BASE_URL}`); - await page.evaluate(() => { - localStorage.setItem('antd-pro-authority', '["admin"]'); - }); -}); - -describe('Ant Design Pro E2E test', () => { - const testPage = (path) => async () => { - await page.goto(`${BASE_URL}${path}`); - await page.waitForSelector('footer', { - timeout: 2000, - }); - const haveFooter = await page.evaluate( - () => document.getElementsByTagName('footer').length > 0, - ); - expect(haveFooter).toBeTruthy(); - }; - - const routers = formatter(RouterConfig); - routers.forEach((route) => { - it(`test pages ${route}`, testPage(route)); - }); - - it('topmenu should have footer', async () => { - const params = '?navTheme=light&layout=topmenu'; - await page.goto(`${BASE_URL}${params}`); - await page.waitForSelector('footer', { - timeout: 2000, - }); - - const haveFooter = await page.evaluate( - () => document.getElementsByTagName('footer').length > 0, - ); - expect(haveFooter).toBeTruthy(); - }); -}); diff --git a/src/e2e/baseLayout.e2e.spec.ts b/src/e2e/baseLayout.e2e.spec.ts new file mode 100644 index 00000000..e1d62c71 --- /dev/null +++ b/src/e2e/baseLayout.e2e.spec.ts @@ -0,0 +1,45 @@ +import type { Page } from '@playwright/test'; +import { test, expect } from '@playwright/test'; +const { uniq } = require('lodash'); +const RouterConfig = require('../../config/routes').default; + +const BASE_URL = `http://localhost:${process.env.PORT || 8001}`; + +function formatter(routes: any, parentPath = ''): string[] { + const fixedParentPath = parentPath.replace(/\/{1,}/g, '/'); + let result: string[] = []; + routes.forEach((item: { path: string; routes: string }) => { + if (item.path && !item.path.startsWith('/')) { + result.push(`${fixedParentPath}/${item.path}`.replace(/\/{1,}/g, '/')); + } + if (item.path && item.path.startsWith('/')) { + result.push(`${item.path}`.replace(/\/{1,}/g, '/')); + } + if (item.routes) { + result = result.concat( + formatter(item.routes, item.path ? `${fixedParentPath}/${item.path}` : parentPath), + ); + } + }); + return uniq(result.filter((item) => !!item)); +} + +const testPage = (path: string, page: Page) => async () => { + await page.evaluate(() => { + localStorage.setItem('antd-pro-authority', '["admin"]'); + }); + await page.goto(`${BASE_URL}${path}`); + await page.waitForSelector('footer', { + timeout: 2000, + }); + const haveFooter = await page.evaluate(() => document.getElementsByTagName('footer').length > 0); + expect(haveFooter).toBeTruthy(); +}; + +const routers = formatter(RouterConfig); + +routers.forEach((route) => { + test(`test route page ${route}`, async ({ page }) => { + await testPage(route, page); + }); +}); diff --git a/tests/PuppeteerEnvironment.js b/tests/PuppeteerEnvironment.js deleted file mode 100644 index 13409be1..00000000 --- a/tests/PuppeteerEnvironment.js +++ /dev/null @@ -1,41 +0,0 @@ -// eslint-disable-next-line -const NodeEnvironment = require('jest-environment-node'); -const getBrowser = require('./getBrowser'); - -class PuppeteerEnvironment extends NodeEnvironment { - // Jest is not available here, so we have to reverse engineer - // the setTimeout function, see https://github.com/facebook/jest/blob/v23.1.0/packages/jest-runtime/src/index.js#L823 - setTimeout(timeout) { - if (this.global.jasmine) { - // eslint-disable-next-line no-underscore-dangle - this.global.jasmine.DEFAULT_TIMEOUT_INTERVAL = timeout; - } else { - this.global[Symbol.for('TEST_TIMEOUT_SYMBOL')] = timeout; - } - } - - async setup() { - const browser = await getBrowser(); - const page = await browser.newPage(); - this.global.browser = browser; - this.global.page = page; - } - - async teardown() { - const { page, browser } = this.global; - - if (page) { - await page.close(); - } - - if (browser) { - await browser.disconnect(); - } - - if (browser) { - await browser.close(); - } - } -} - -module.exports = PuppeteerEnvironment; diff --git a/tests/beforeTest.js b/tests/beforeTest.js deleted file mode 100644 index 226ea120..00000000 --- a/tests/beforeTest.js +++ /dev/null @@ -1,39 +0,0 @@ -/* eslint-disable global-require */ -/* eslint-disable import/no-extraneous-dependencies */ -const { execSync } = require('child_process'); -const { join } = require('path'); -const findChrome = require('carlo/lib/find_chrome'); -const detectInstaller = require('detect-installer'); - -const installPuppeteer = () => { - // find can use package manager - const packages = detectInstaller(join(__dirname, '../')); - // get installed package manager - const packageName = packages.find(detectInstaller.hasPackageCommand) || 'npm'; - console.log(`🤖 will use ${packageName} install puppeteer`); - const command = `${packageName} ${packageName.includes('yarn') ? 'add' : 'i'} puppeteer`; - execSync(command, { - stdio: 'inherit', - }); -}; - -const initPuppeteer = async () => { - try { - // eslint-disable-next-line import/no-unresolved - const findChromePath = await findChrome({}); - const { executablePath } = findChromePath; - console.log(`🧲 find you browser in ${executablePath}`); - return; - } catch (error) { - console.log('🧲 no find chrome'); - } - - try { - require.resolve('puppeteer'); - } catch (error) { - // need install puppeteer - await installPuppeteer(); - } -}; - -initPuppeteer(); diff --git a/tests/getBrowser.js b/tests/getBrowser.js deleted file mode 100644 index 3b4ef331..00000000 --- a/tests/getBrowser.js +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint-disable global-require */ -/* eslint-disable import/no-extraneous-dependencies */ -const findChrome = require('carlo/lib/find_chrome'); - -const getBrowser = async () => { - try { - // eslint-disable-next-line import/no-unresolved - const puppeteer = require('puppeteer'); - const browser = await puppeteer.launch({ - args: [ - '--disable-gpu', - '--disable-dev-shm-usage', - '--no-first-run', - '--no-zygote', - '--no-sandbox', - ], - }); - return browser; - } catch (error) { - // console.log(error) - } - - try { - // eslint-disable-next-line import/no-unresolved - const puppeteer = require('puppeteer-core'); - const findChromePath = await findChrome({}); - const { executablePath } = findChromePath; - const browser = await puppeteer.launch({ - executablePath, - args: [ - '--disable-gpu', - '--disable-dev-shm-usage', - '--no-first-run', - '--no-zygote', - '--no-sandbox', - ], - }); - return browser; - } catch (error) { - console.log('🧲 no find chrome'); - } - throw new Error('no find puppeteer'); -}; - -module.exports = getBrowser; diff --git a/tests/run-tests.js b/tests/run-tests.js index 368cd5e3..8ac1d871 100644 --- a/tests/run-tests.js +++ b/tests/run-tests.js @@ -1,8 +1,5 @@ -/* eslint-disable eslint-comments/disable-enable-pair */ /* eslint-disable @typescript-eslint/no-var-requires */ -/* eslint-disable eslint-comments/no-unlimited-disable */ const { spawn } = require('child_process'); -// eslint-disable-next-line import/no-extraneous-dependencies const { kill } = require('cross-port-killer'); const env = Object.create(process.env); @@ -36,7 +33,7 @@ startServer.stdout.on('data', (data) => { console.log('Development server is started, ready to run tests.'); const testCmd = spawn( /^win/.test(process.platform) ? 'npm.cmd' : 'npm', - ['test', '--', '--maxWorkers=1', '--runInBand'], + ['run', 'playwright'], { stdio: 'inherit', }, diff --git a/tsconfig.json b/tsconfig.json index 90a06413..e7a27859 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,6 +26,7 @@ "include": [ "mock/**/*", "src/**/*", + "playwright.config.ts", "tests/**/*", "test/**/*", "__test__/**/*",