mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
test: improve ui test code
This commit is contained in:
parent
7946ecd865
commit
8ce3a993a9
4 changed files with 72 additions and 100 deletions
|
@ -30,8 +30,9 @@
|
||||||
"@silverhand/eslint-config": "2.0.1",
|
"@silverhand/eslint-config": "2.0.1",
|
||||||
"@silverhand/essentials": "2.3.0",
|
"@silverhand/essentials": "2.3.0",
|
||||||
"@silverhand/ts-config": "2.0.3",
|
"@silverhand/ts-config": "2.0.3",
|
||||||
|
"@types/expect-puppeteer": "^5.0.3",
|
||||||
"@types/jest": "^29.1.2",
|
"@types/jest": "^29.1.2",
|
||||||
"@types/jest-environment-puppeteer": "^5.0.2",
|
"@types/jest-environment-puppeteer": "^5.0.3",
|
||||||
"@types/node": "^18.11.18",
|
"@types/node": "^18.11.18",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"eslint": "^8.34.0",
|
"eslint": "^8.34.0",
|
||||||
|
|
|
@ -10,6 +10,8 @@ const appendPathname = (pathname: string, baseUrl: URL) =>
|
||||||
* NOTE: This test suite assumes test cases will run sequentially (which is Jest default).
|
* NOTE: This test suite assumes test cases will run sequentially (which is Jest default).
|
||||||
* Parallel execution will lead to errors.
|
* 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', () => {
|
describe('smoke testing for cloud', () => {
|
||||||
const consoleUsername = 'admin';
|
const consoleUsername = 'admin';
|
||||||
const consolePassword = generatePassword();
|
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
|
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 () => {
|
it('opens with app element and navigates to sign-in page', async () => {
|
||||||
const navigation = page.waitForNavigation({ waitUntil: 'networkidle0' });
|
|
||||||
await page.goto(logtoCloudUrl.href);
|
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);
|
expect(page.url()).toBe(appendPathname('/register', adminTenantUrl).href);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('registers the first admin account', async () => {
|
it('registers the first admin account', async () => {
|
||||||
const createAccountButton = await page.waitForSelector('button');
|
await expect(page).toClick('button', { text: 'Create account' });
|
||||||
expect(createAccountButton).not.toBeNull();
|
|
||||||
|
|
||||||
const usernameField = await page.waitForSelector('input[name=identifier]');
|
await expect(page).toFill('input[name=identifier]', consoleUsername);
|
||||||
const submitButton = await page.waitForSelector('button[name=submit]');
|
await expect(page).toClick('button[name=submit]');
|
||||||
|
|
||||||
await usernameField.type(consoleUsername);
|
|
||||||
|
|
||||||
const navigateToCreatePassword = page.waitForNavigation({ waitUntil: 'networkidle0' });
|
|
||||||
await submitButton.click();
|
|
||||||
await navigateToCreatePassword;
|
|
||||||
|
|
||||||
|
await page.waitForNavigation({ waitUntil: 'networkidle0' });
|
||||||
expect(page.url()).toBe(appendPathname('/register/password', adminTenantUrl).href);
|
expect(page.url()).toBe(appendPathname('/register/password', adminTenantUrl).href);
|
||||||
|
|
||||||
const passwordField = await page.waitForSelector('input[name=newPassword]');
|
await expect(page).toFillForm('form', {
|
||||||
const confirmPasswordField = await page.waitForSelector('input[name=confirmPassword]');
|
newPassword: consolePassword,
|
||||||
const saveButton = await page.waitForSelector('button[name=submit]');
|
confirmPassword: consolePassword,
|
||||||
await passwordField.type(consolePassword);
|
});
|
||||||
await confirmPasswordField.type(consolePassword);
|
await expect(page).toClick('button[name=submit]');
|
||||||
|
await page.waitForNavigation({ waitUntil: 'networkidle0' });
|
||||||
const navigateToCloud = page.waitForNavigation({ waitUntil: 'networkidle0' });
|
|
||||||
await saveButton.click();
|
|
||||||
await navigateToCloud;
|
|
||||||
|
|
||||||
expect(page.url()).toBe(logtoCloudUrl.href);
|
expect(page.url()).toBe(logtoCloudUrl.href);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows a tenant-select page with two tenants', async () => {
|
it('shows a tenant-select page with two tenants', async () => {
|
||||||
const tenantsWrapper = await page.waitForSelector('div[class$=wrapper]');
|
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))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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';
|
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).
|
* NOTE: This test suite assumes test cases will run sequentially (which is Jest default).
|
||||||
* Parallel execution will lead to errors.
|
* 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', () => {
|
describe('smoke testing', () => {
|
||||||
const consoleUsername = 'admin';
|
const consoleUsername = 'admin';
|
||||||
const consolePassword = generatePassword();
|
const consolePassword = generatePassword();
|
||||||
|
|
||||||
it('opens with app element and navigates to welcome page', async () => {
|
it('opens with app element and navigates to welcome page', async () => {
|
||||||
const navigation = page.waitForNavigation({ waitUntil: 'networkidle0' });
|
await page.goto(logtoConsoleUrl.href);
|
||||||
await page.goto(logtoConsoleUrl);
|
await page.waitForNavigation({ waitUntil: 'networkidle0' });
|
||||||
await navigation;
|
|
||||||
|
|
||||||
await expect(page.waitForSelector('#app')).resolves.not.toBeNull();
|
await expect(page).toMatchElement('#app');
|
||||||
expect(page.url()).toBe(new URL('console/welcome', logtoConsoleUrl).href);
|
expect(page.url()).toBe(new URL('console/welcome', logtoConsoleUrl).href);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('registers a new admin account and automatically signs in', async () => {
|
it('registers a new admin account and automatically signs in', async () => {
|
||||||
const createAccountButton = await page.waitForSelector('button');
|
await expect(page).toClick('button', { text: 'Create account' });
|
||||||
expect(createAccountButton).not.toBeNull();
|
|
||||||
|
|
||||||
const navigateToRegister = page.waitForNavigation({ waitUntil: 'networkidle0' });
|
|
||||||
await createAccountButton.click();
|
|
||||||
await navigateToRegister;
|
|
||||||
|
|
||||||
|
await page.waitForNavigation({ waitUntil: 'networkidle0' });
|
||||||
expect(page.url()).toBe(new URL('register', logtoConsoleUrl).href);
|
expect(page.url()).toBe(new URL('register', logtoConsoleUrl).href);
|
||||||
|
|
||||||
const usernameField = await page.waitForSelector('input[name=identifier]');
|
await expect(page).toFill('input[name=identifier]', consoleUsername);
|
||||||
const submitButton = await page.waitForSelector('button[name=submit]');
|
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 expect(page).toFillForm('form', {
|
||||||
await submitButton.click();
|
newPassword: consolePassword,
|
||||||
await navigateToSignIn;
|
confirmPassword: consolePassword,
|
||||||
|
});
|
||||||
|
|
||||||
expect(page.url()).toBe(new URL('register/password', logtoConsoleUrl).href);
|
await expect(page).toClick('button[name=submit]');
|
||||||
|
await page.waitForNavigation({ waitUntil: 'networkidle0' });
|
||||||
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;
|
|
||||||
|
|
||||||
expect(page.url()).toBe(new URL('console/get-started', logtoConsoleUrl).href);
|
expect(page.url()).toBe(new URL('console/get-started', logtoConsoleUrl).href);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('signs out of admin console', async () => {
|
it('signs out of admin console', async () => {
|
||||||
const userElement = await page.waitForSelector('div[class$=topbar] > div[class$=container]');
|
await expect(page).toClick('div[class$=topbar] > div[class$=container]');
|
||||||
await userElement.click();
|
|
||||||
|
|
||||||
// Try awaiting for 500ms before clicking sign-out button
|
// Try awaiting for 500ms before clicking sign-out button
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
|
|
||||||
const signOutButton = await page.waitForSelector(
|
await expect(page).toClick(
|
||||||
'.ReactModalPortal div[class$=dropdownContainer] div[class$=dropdownItem]:last-child'
|
'.ReactModalPortal div[class$=dropdownContainer] div[class$=dropdownItem]:last-child'
|
||||||
);
|
);
|
||||||
const navigation = page.waitForNavigation({ waitUntil: 'networkidle0' });
|
await page.waitForNavigation({ waitUntil: 'networkidle0' });
|
||||||
await signOutButton.click();
|
|
||||||
await navigation;
|
|
||||||
|
|
||||||
expect(page.url()).toBe(new URL('sign-in', logtoConsoleUrl).href);
|
expect(page.url()).toBe(new URL('sign-in', logtoConsoleUrl).href);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('signs in to admin console', async () => {
|
it('signs in to admin console', async () => {
|
||||||
const usernameField = await page.waitForSelector('input[name=identifier]');
|
await expect(page).toFillForm('form', {
|
||||||
const passwordField = await page.waitForSelector('input[name=password]');
|
identifier: consoleUsername,
|
||||||
const submitButton = await page.waitForSelector('button[name=submit]');
|
password: consolePassword,
|
||||||
|
});
|
||||||
await usernameField.type(consoleUsername);
|
await expect(page).toClick('button[name=submit]');
|
||||||
await passwordField.type(consolePassword);
|
await page.waitForNavigation({ waitUntil: 'networkidle0' });
|
||||||
|
|
||||||
const navigation = page.waitForNavigation({ waitUntil: 'networkidle0' });
|
|
||||||
await submitButton.click();
|
|
||||||
await navigation;
|
|
||||||
|
|
||||||
expect(page.url()).toBe(new URL('console/get-started', logtoConsoleUrl).href);
|
expect(page.url()).toBe(new URL('console/get-started', logtoConsoleUrl).href);
|
||||||
|
|
||||||
const userElement = await page.waitForSelector('div[class$=topbar] > div:last-child');
|
await expect(page).toClick('div[class$=topbar] > div:last-child');
|
||||||
await userElement.click();
|
|
||||||
|
|
||||||
const userMenu = await page.waitForSelector('.ReactModalPortal div[class$=dropdownContainer]');
|
const userMenu = await page.waitForSelector('.ReactModalPortal div[class$=dropdownContainer]');
|
||||||
const usernameString = await userMenu.$eval(
|
await expect(userMenu).toMatchElement('div[class$=nameWrapper] > div[class$=name]', {
|
||||||
'div[class$=nameWrapper] > div[class$=name]',
|
text: consoleUsername,
|
||||||
(element) => element.textContent
|
});
|
||||||
);
|
|
||||||
expect(usernameString).toBe(consoleUsername);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders SVG correctly with viewbox property', async () => {
|
it('renders SVG correctly with viewbox property', async () => {
|
||||||
const logoSvg = await page.waitForSelector('div[class$=topbar] > svg[viewbox]');
|
await page.waitForSelector('div[class$=topbar] > svg[viewbox]');
|
||||||
|
|
||||||
expect(logoSvg).not.toBeNull();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -545,8 +545,9 @@ importers:
|
||||||
'@silverhand/eslint-config': 2.0.1
|
'@silverhand/eslint-config': 2.0.1
|
||||||
'@silverhand/essentials': 2.3.0
|
'@silverhand/essentials': 2.3.0
|
||||||
'@silverhand/ts-config': 2.0.3
|
'@silverhand/ts-config': 2.0.3
|
||||||
|
'@types/expect-puppeteer': ^5.0.3
|
||||||
'@types/jest': ^29.1.2
|
'@types/jest': ^29.1.2
|
||||||
'@types/jest-environment-puppeteer': ^5.0.2
|
'@types/jest-environment-puppeteer': ^5.0.3
|
||||||
'@types/node': ^18.11.18
|
'@types/node': ^18.11.18
|
||||||
'@withtyped/server': ^0.8.0
|
'@withtyped/server': ^0.8.0
|
||||||
dotenv: ^16.0.0
|
dotenv: ^16.0.0
|
||||||
|
@ -573,8 +574,9 @@ importers:
|
||||||
'@silverhand/eslint-config': 2.0.1_kjzxg5porcw5dx54sezsklj5cy
|
'@silverhand/eslint-config': 2.0.1_kjzxg5porcw5dx54sezsklj5cy
|
||||||
'@silverhand/essentials': 2.3.0
|
'@silverhand/essentials': 2.3.0
|
||||||
'@silverhand/ts-config': 2.0.3_typescript@4.9.4
|
'@silverhand/ts-config': 2.0.3_typescript@4.9.4
|
||||||
|
'@types/expect-puppeteer': 5.0.3
|
||||||
'@types/jest': 29.1.2
|
'@types/jest': 29.1.2
|
||||||
'@types/jest-environment-puppeteer': 5.0.2
|
'@types/jest-environment-puppeteer': 5.0.3
|
||||||
'@types/node': 18.11.18
|
'@types/node': 18.11.18
|
||||||
dotenv: 16.0.0
|
dotenv: 16.0.0
|
||||||
eslint: 8.34.0
|
eslint: 8.34.0
|
||||||
|
@ -4032,6 +4034,13 @@ packages:
|
||||||
'@types/node': 18.11.18
|
'@types/node': 18.11.18
|
||||||
dev: true
|
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:
|
/@types/express-serve-static-core/4.17.26:
|
||||||
resolution: {integrity: sha512-zeu3tpouA043RHxW0gzRxwCHchMgftE8GArRsvYT0ByDMbn19olQHx5jLue0LxWY6iYtXb7rXmuVtSkhy9YZvQ==}
|
resolution: {integrity: sha512-zeu3tpouA043RHxW0gzRxwCHchMgftE8GArRsvYT0ByDMbn19olQHx5jLue0LxWY6iYtXb7rXmuVtSkhy9YZvQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -4121,8 +4130,8 @@ packages:
|
||||||
'@types/istanbul-lib-report': 3.0.0
|
'@types/istanbul-lib-report': 3.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/jest-environment-puppeteer/5.0.2:
|
/@types/jest-environment-puppeteer/5.0.3:
|
||||||
resolution: {integrity: sha512-YCdegQnDou6aIK8wqygDDctHgJZtlXd03fI9Bgbqkdu66EFKnImmt7auiR9OkxkSSiqS9smn0joX2pGpVs7ErA==}
|
resolution: {integrity: sha512-vWGfeb+0TOPZy7+VscKURWzE5lzYjclSWLxtjVpDAYcjUv8arAS1av06xK3mpgeNCDVx7XvavD8Elq1a4w9wIA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/types': 27.5.1
|
'@jest/types': 27.5.1
|
||||||
'@types/puppeteer': 5.4.6
|
'@types/puppeteer': 5.4.6
|
||||||
|
@ -4416,10 +4425,6 @@ packages:
|
||||||
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
|
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/yargs-parser/20.2.1:
|
|
||||||
resolution: {integrity: sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/yargs-parser/21.0.0:
|
/@types/yargs-parser/21.0.0:
|
||||||
resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==}
|
resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -4427,7 +4432,7 @@ packages:
|
||||||
/@types/yargs/16.0.4:
|
/@types/yargs/16.0.4:
|
||||||
resolution: {integrity: sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==}
|
resolution: {integrity: sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/yargs-parser': 20.2.1
|
'@types/yargs-parser': 21.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/yargs/17.0.13:
|
/@types/yargs/17.0.13:
|
||||||
|
@ -9441,7 +9446,7 @@ packages:
|
||||||
'@jest/types': 27.5.1
|
'@jest/types': 27.5.1
|
||||||
'@types/node': 18.11.18
|
'@types/node': 18.11.18
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
ci-info: 3.5.0
|
ci-info: 3.8.0
|
||||||
graceful-fs: 4.2.10
|
graceful-fs: 4.2.10
|
||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
Loading…
Reference in a new issue