0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

Merge pull request #3299 from logto-io/gao-improve-ui-test

test: improve ui test code
This commit is contained in:
Gao Sun 2023-03-08 10:44:48 +08:00 committed by GitHub
commit e5b055f173
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 111 additions and 106 deletions

View file

@ -51,7 +51,7 @@ const Tenants = ({ data, onAdd }: Props) => {
</a>
))}
<h3>Create a tenant</h3>
<Button title={<DangerousRaw>Create New</DangerousRaw>} onClick={createTenant} />
<Button title={<DangerousRaw>Create</DangerousRaw>} onClick={createTenant} />
</div>
);
}

View file

@ -30,8 +30,9 @@
"@silverhand/eslint-config": "2.0.1",
"@silverhand/essentials": "2.4.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",

View file

@ -10,64 +10,78 @@ 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();
const logtoCloudUrl = new URL(logtoCloudUrlString);
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' });
it('can open with app element and navigate to register page', async () => {
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();
it('can register the first admin account', async () => {
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();
})
);
await expect(tenantsWrapper).toMatchElement('a:nth-of-type(1)', { text: 'default' });
await expect(tenantsWrapper).toMatchElement('a:nth-of-type(2)', { text: 'admin' });
});
expect(
['default', 'admin'].every((tenantId) =>
hrefs.some((href) => String(href).endsWith('/' + tenantId))
)
it('can create another tenant', async () => {
await expect(page).toClick('button', { text: 'Create' });
await page.waitForTimeout(1000);
const tenants = await page.$$('div[class$=wrapper] > a');
expect(tenants.length).toBe(3);
});
it('can enter the tenant just created', async () => {
const button = await page.waitForSelector('div[class$=wrapper] > a:last-of-type');
const tenantId = await button.evaluate((element) => element.textContent);
await button.click();
// Wait for our beautiful logto to show up
await page.waitForSelector('div[class$=topbar] > svg[viewbox][class$=logo]');
expect(page.url()).toBe(new URL(`/${tenantId ?? ''}/onboard/welcome`, logtoCloudUrl).href);
});
it('can sign out of admin console', async () => {
await expect(page).toClick('div[class$=topbar] > div[class$=container]');
// Try awaiting for 500ms before clicking sign-out button
await page.waitForTimeout(500);
await expect(page).toClick(
'.ReactModalPortal div[class$=dropdownContainer] div[class$=dropdownItem]:last-child'
);
await page.waitForNavigation({ waitUntil: 'networkidle0' });
expect(page.url()).toBe(new URL('sign-in', logtoConsoleUrl).href);
});
});

View file

@ -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;
it('can open with app element and navigate to welcome page', async () => {
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;
it('can register a new admin account and automatically sign in', async () => {
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();
it('can sign out of admin console', async () => {
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;
it('can sign in to admin console', async () => {
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][class$=logo]');
});
});

View file

@ -2,7 +2,7 @@ const welcome = {
title: 'Welcome to Admin Console',
description:
'Admin console is a web app to manage Logto without coding requirements. Lets first create an account. With this account, you can manage Logto by yourself or on behalf of your company.',
create_account: 'Create Account',
create_account: 'Create account',
};
export default welcome;

View file

@ -551,8 +551,9 @@ importers:
'@silverhand/eslint-config': 2.0.1
'@silverhand/essentials': 2.4.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
@ -579,8 +580,9 @@ importers:
'@silverhand/eslint-config': 2.0.1_kjzxg5porcw5dx54sezsklj5cy
'@silverhand/essentials': 2.4.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
@ -4135,6 +4137,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:
@ -4224,8 +4233,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
@ -4532,10 +4541,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
@ -4543,7 +4548,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:
@ -9634,7 +9639,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