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:
commit
e5b055f173
6 changed files with 111 additions and 106 deletions
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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]');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@ const welcome = {
|
|||
title: 'Welcome to Admin Console',
|
||||
description:
|
||||
'Admin console is a web app to manage Logto without coding requirements. Let’s 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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue