From 8ce3a993a97820c7987846a0ee1d06cea1424d01 Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Mon, 6 Mar 2023 17:44:48 +0800 Subject: [PATCH 1/2] test: improve ui test code --- packages/integration-tests/package.json | 3 +- .../src/tests/ui-cloud/smoke.test.ts | 51 ++++------ .../src/tests/ui/smoke.test.ts | 93 ++++++++----------- pnpm-lock.yaml | 25 +++-- 4 files changed, 72 insertions(+), 100 deletions(-) diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index de2d1da1c..9c2ff1975 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -30,8 +30,9 @@ "@silverhand/eslint-config": "2.0.1", "@silverhand/essentials": "2.3.0", "@silverhand/ts-config": "2.0.3", + "@types/expect-puppeteer": "^5.0.3", "@types/jest": "^29.1.2", - "@types/jest-environment-puppeteer": "^5.0.2", + "@types/jest-environment-puppeteer": "^5.0.3", "@types/node": "^18.11.18", "dotenv": "^16.0.0", "eslint": "^8.34.0", diff --git a/packages/integration-tests/src/tests/ui-cloud/smoke.test.ts b/packages/integration-tests/src/tests/ui-cloud/smoke.test.ts index 63bad1f9b..fdbe29d5d 100644 --- a/packages/integration-tests/src/tests/ui-cloud/smoke.test.ts +++ b/packages/integration-tests/src/tests/ui-cloud/smoke.test.ts @@ -10,6 +10,8 @@ const appendPathname = (pathname: string, baseUrl: URL) => * NOTE: This test suite assumes test cases will run sequentially (which is Jest default). * Parallel execution will lead to errors. */ +// Tip: See https://github.com/argos-ci/jest-puppeteer/blob/main/packages/expect-puppeteer/README.md +// for convenient expect methods describe('smoke testing for cloud', () => { const consoleUsername = 'admin'; const consolePassword = generatePassword(); @@ -17,57 +19,36 @@ describe('smoke testing for cloud', () => { const adminTenantUrl = new URL(logtoConsoleUrl); // In dev mode, the console URL is actually for admin tenant it('opens with app element and navigates to sign-in page', async () => { - const navigation = page.waitForNavigation({ waitUntil: 'networkidle0' }); await page.goto(logtoCloudUrl.href); - await navigation; + await page.waitForNavigation({ waitUntil: 'networkidle0' }); - await expect(page.waitForSelector('#app')).resolves.not.toBeNull(); + await expect(page).toMatchElement('#app'); expect(page.url()).toBe(appendPathname('/register', adminTenantUrl).href); }); it('registers the first admin account', async () => { - const createAccountButton = await page.waitForSelector('button'); - expect(createAccountButton).not.toBeNull(); + await expect(page).toClick('button', { text: 'Create account' }); - const usernameField = await page.waitForSelector('input[name=identifier]'); - const submitButton = await page.waitForSelector('button[name=submit]'); - - await usernameField.type(consoleUsername); - - const navigateToCreatePassword = page.waitForNavigation({ waitUntil: 'networkidle0' }); - await submitButton.click(); - await navigateToCreatePassword; + await expect(page).toFill('input[name=identifier]', consoleUsername); + await expect(page).toClick('button[name=submit]'); + await page.waitForNavigation({ waitUntil: 'networkidle0' }); expect(page.url()).toBe(appendPathname('/register/password', adminTenantUrl).href); - const passwordField = await page.waitForSelector('input[name=newPassword]'); - const confirmPasswordField = await page.waitForSelector('input[name=confirmPassword]'); - const saveButton = await page.waitForSelector('button[name=submit]'); - await passwordField.type(consolePassword); - await confirmPasswordField.type(consolePassword); - - const navigateToCloud = page.waitForNavigation({ waitUntil: 'networkidle0' }); - await saveButton.click(); - await navigateToCloud; + await expect(page).toFillForm('form', { + newPassword: consolePassword, + confirmPassword: consolePassword, + }); + await expect(page).toClick('button[name=submit]'); + await page.waitForNavigation({ waitUntil: 'networkidle0' }); expect(page.url()).toBe(logtoCloudUrl.href); }); it('shows a tenant-select page with two tenants', async () => { const tenantsWrapper = await page.waitForSelector('div[class$=wrapper]'); - const tenants = await tenantsWrapper.$$('a'); - const hrefs = await Promise.all( - tenants.map(async (element) => { - const value = await element.getProperty('href'); - return value.jsonValue(); - }) - ); - - expect( - ['default', 'admin'].every((tenantId) => - hrefs.some((href) => String(href).endsWith('/' + tenantId)) - ) - ); + await expect(tenantsWrapper).toMatchElement('a:nth-of-type(1)', { text: 'default' }); + await expect(tenantsWrapper).toMatchElement('a:nth-of-type(2)', { text: 'admin' }); }); }); diff --git a/packages/integration-tests/src/tests/ui/smoke.test.ts b/packages/integration-tests/src/tests/ui/smoke.test.ts index 47b8c0ab8..799ef7727 100644 --- a/packages/integration-tests/src/tests/ui/smoke.test.ts +++ b/packages/integration-tests/src/tests/ui/smoke.test.ts @@ -1,102 +1,87 @@ -import { logtoConsoleUrl } from '#src/constants.js'; +import path from 'path'; + +import { logtoConsoleUrl as logtoConsoleUrlString } from '#src/constants.js'; import { generatePassword } from '#src/utils.js'; +const appendPathname = (pathname: string, baseUrl: URL) => + new URL(path.join(baseUrl.pathname, pathname), baseUrl); + +const logtoConsoleUrl = new URL(logtoConsoleUrlString); + /** * NOTE: This test suite assumes test cases will run sequentially (which is Jest default). * Parallel execution will lead to errors. */ +// Tip: See https://github.com/argos-ci/jest-puppeteer/blob/main/packages/expect-puppeteer/README.md +// for convenient expect methods describe('smoke testing', () => { const consoleUsername = 'admin'; const consolePassword = generatePassword(); it('opens with app element and navigates to welcome page', async () => { - const navigation = page.waitForNavigation({ waitUntil: 'networkidle0' }); - await page.goto(logtoConsoleUrl); - await navigation; + await page.goto(logtoConsoleUrl.href); + await page.waitForNavigation({ waitUntil: 'networkidle0' }); - await expect(page.waitForSelector('#app')).resolves.not.toBeNull(); + await expect(page).toMatchElement('#app'); expect(page.url()).toBe(new URL('console/welcome', logtoConsoleUrl).href); }); it('registers a new admin account and automatically signs in', async () => { - const createAccountButton = await page.waitForSelector('button'); - expect(createAccountButton).not.toBeNull(); - - const navigateToRegister = page.waitForNavigation({ waitUntil: 'networkidle0' }); - await createAccountButton.click(); - await navigateToRegister; + await expect(page).toClick('button', { text: 'Create account' }); + await page.waitForNavigation({ waitUntil: 'networkidle0' }); expect(page.url()).toBe(new URL('register', logtoConsoleUrl).href); - const usernameField = await page.waitForSelector('input[name=identifier]'); - const submitButton = await page.waitForSelector('button[name=submit]'); + await expect(page).toFill('input[name=identifier]', consoleUsername); + await expect(page).toClick('button[name=submit]'); - await usernameField.type(consoleUsername); + await page.waitForNavigation({ waitUntil: 'networkidle0' }); + expect(page.url()).toBe(appendPathname('/register/password', logtoConsoleUrl).href); - const navigateToSignIn = page.waitForNavigation({ waitUntil: 'networkidle0' }); - await submitButton.click(); - await navigateToSignIn; + await expect(page).toFillForm('form', { + newPassword: consolePassword, + confirmPassword: consolePassword, + }); - expect(page.url()).toBe(new URL('register/password', logtoConsoleUrl).href); - - const passwordField = await page.waitForSelector('input[name=newPassword]'); - const confirmPasswordField = await page.waitForSelector('input[name=confirmPassword]'); - const saveButton = await page.waitForSelector('button[name=submit]'); - await passwordField.type(consolePassword); - await confirmPasswordField.type(consolePassword); - - const navigateToGetStarted = page.waitForNavigation({ waitUntil: 'networkidle0' }); - await saveButton.click(); - await navigateToGetStarted; + await expect(page).toClick('button[name=submit]'); + await page.waitForNavigation({ waitUntil: 'networkidle0' }); expect(page.url()).toBe(new URL('console/get-started', logtoConsoleUrl).href); }); it('signs out of admin console', async () => { - const userElement = await page.waitForSelector('div[class$=topbar] > div[class$=container]'); - await userElement.click(); + await expect(page).toClick('div[class$=topbar] > div[class$=container]'); // Try awaiting for 500ms before clicking sign-out button await page.waitForTimeout(500); - const signOutButton = await page.waitForSelector( + await expect(page).toClick( '.ReactModalPortal div[class$=dropdownContainer] div[class$=dropdownItem]:last-child' ); - const navigation = page.waitForNavigation({ waitUntil: 'networkidle0' }); - await signOutButton.click(); - await navigation; + await page.waitForNavigation({ waitUntil: 'networkidle0' }); expect(page.url()).toBe(new URL('sign-in', logtoConsoleUrl).href); }); it('signs in to admin console', async () => { - const usernameField = await page.waitForSelector('input[name=identifier]'); - const passwordField = await page.waitForSelector('input[name=password]'); - const submitButton = await page.waitForSelector('button[name=submit]'); - - await usernameField.type(consoleUsername); - await passwordField.type(consolePassword); - - const navigation = page.waitForNavigation({ waitUntil: 'networkidle0' }); - await submitButton.click(); - await navigation; + await expect(page).toFillForm('form', { + identifier: consoleUsername, + password: consolePassword, + }); + await expect(page).toClick('button[name=submit]'); + await page.waitForNavigation({ waitUntil: 'networkidle0' }); expect(page.url()).toBe(new URL('console/get-started', logtoConsoleUrl).href); - const userElement = await page.waitForSelector('div[class$=topbar] > div:last-child'); - await userElement.click(); + await expect(page).toClick('div[class$=topbar] > div:last-child'); const userMenu = await page.waitForSelector('.ReactModalPortal div[class$=dropdownContainer]'); - const usernameString = await userMenu.$eval( - 'div[class$=nameWrapper] > div[class$=name]', - (element) => element.textContent - ); - expect(usernameString).toBe(consoleUsername); + await expect(userMenu).toMatchElement('div[class$=nameWrapper] > div[class$=name]', { + text: consoleUsername, + }); }); it('renders SVG correctly with viewbox property', async () => { - const logoSvg = await page.waitForSelector('div[class$=topbar] > svg[viewbox]'); - - expect(logoSvg).not.toBeNull(); + await page.waitForSelector('div[class$=topbar] > svg[viewbox]'); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b1a78282e..688282715 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -545,8 +545,9 @@ importers: '@silverhand/eslint-config': 2.0.1 '@silverhand/essentials': 2.3.0 '@silverhand/ts-config': 2.0.3 + '@types/expect-puppeteer': ^5.0.3 '@types/jest': ^29.1.2 - '@types/jest-environment-puppeteer': ^5.0.2 + '@types/jest-environment-puppeteer': ^5.0.3 '@types/node': ^18.11.18 '@withtyped/server': ^0.8.0 dotenv: ^16.0.0 @@ -573,8 +574,9 @@ importers: '@silverhand/eslint-config': 2.0.1_kjzxg5porcw5dx54sezsklj5cy '@silverhand/essentials': 2.3.0 '@silverhand/ts-config': 2.0.3_typescript@4.9.4 + '@types/expect-puppeteer': 5.0.3 '@types/jest': 29.1.2 - '@types/jest-environment-puppeteer': 5.0.2 + '@types/jest-environment-puppeteer': 5.0.3 '@types/node': 18.11.18 dotenv: 16.0.0 eslint: 8.34.0 @@ -4032,6 +4034,13 @@ packages: '@types/node': 18.11.18 dev: true + /@types/expect-puppeteer/5.0.3: + resolution: {integrity: sha512-NIqATm95VmFbc2s9v1L3yj9ZS9/rCrtptSgBsvW8mcw2KFpLFQqXPyEbo0Vju1eiBieI38jRGWgpbVuUKfQVoQ==} + dependencies: + '@types/jest': 29.1.2 + '@types/puppeteer': 5.4.6 + dev: true + /@types/express-serve-static-core/4.17.26: resolution: {integrity: sha512-zeu3tpouA043RHxW0gzRxwCHchMgftE8GArRsvYT0ByDMbn19olQHx5jLue0LxWY6iYtXb7rXmuVtSkhy9YZvQ==} dependencies: @@ -4121,8 +4130,8 @@ packages: '@types/istanbul-lib-report': 3.0.0 dev: true - /@types/jest-environment-puppeteer/5.0.2: - resolution: {integrity: sha512-YCdegQnDou6aIK8wqygDDctHgJZtlXd03fI9Bgbqkdu66EFKnImmt7auiR9OkxkSSiqS9smn0joX2pGpVs7ErA==} + /@types/jest-environment-puppeteer/5.0.3: + resolution: {integrity: sha512-vWGfeb+0TOPZy7+VscKURWzE5lzYjclSWLxtjVpDAYcjUv8arAS1av06xK3mpgeNCDVx7XvavD8Elq1a4w9wIA==} dependencies: '@jest/types': 27.5.1 '@types/puppeteer': 5.4.6 @@ -4416,10 +4425,6 @@ packages: resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} dev: true - /@types/yargs-parser/20.2.1: - resolution: {integrity: sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==} - dev: true - /@types/yargs-parser/21.0.0: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} dev: true @@ -4427,7 +4432,7 @@ packages: /@types/yargs/16.0.4: resolution: {integrity: sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==} dependencies: - '@types/yargs-parser': 20.2.1 + '@types/yargs-parser': 21.0.0 dev: true /@types/yargs/17.0.13: @@ -9441,7 +9446,7 @@ packages: '@jest/types': 27.5.1 '@types/node': 18.11.18 chalk: 4.1.2 - ci-info: 3.5.0 + ci-info: 3.8.0 graceful-fs: 4.2.10 picomatch: 2.3.1 dev: true From 6e5123891fc45ef3144a01cfac706f75332c6741 Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Tue, 7 Mar 2023 00:26:31 +0800 Subject: [PATCH 2/2] test: add cloud test cases --- .../console/src/cloud/pages/Main/Tenants.tsx | 2 +- .../src/tests/ui-cloud/smoke.test.ts | 37 ++++++++++++++++++- .../src/tests/ui/smoke.test.ts | 10 ++--- .../en/translation/admin-console/welcome.ts | 2 +- 4 files changed, 42 insertions(+), 9 deletions(-) diff --git a/packages/console/src/cloud/pages/Main/Tenants.tsx b/packages/console/src/cloud/pages/Main/Tenants.tsx index 421b7dac4..64f75ee69 100644 --- a/packages/console/src/cloud/pages/Main/Tenants.tsx +++ b/packages/console/src/cloud/pages/Main/Tenants.tsx @@ -51,7 +51,7 @@ const Tenants = ({ data, onAdd }: Props) => { ))}

Create a tenant

-