0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

test: add ui tests for sign-up and sign-in settings (#4373)

This commit is contained in:
Xiao Yijun 2023-08-18 16:46:00 +08:00 committed by GitHub
parent e13107438c
commit 462f677cdc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 1443 additions and 138 deletions

View file

@ -1,138 +0,0 @@
import { logtoConsoleUrl as logtoConsoleUrlString } from '#src/constants.js';
import { goToAdminConsole, trySaveChanges, waitForToaster } from '#src/ui-helpers/index.js';
import { appendPathname, expectNavigation } from '#src/utils.js';
await page.setViewport({ width: 1920, height: 1080 });
const defaultPrimaryColor = '#6139F6';
const testPrimaryColor = '#5B4D8E';
describe('sign-in experience', () => {
const logtoConsoleUrl = new URL(logtoConsoleUrlString);
beforeAll(async () => {
await goToAdminConsole();
});
it('navigate to sign-in experience page', async () => {
await expectNavigation(
page.goto(appendPathname('/console/sign-in-experience', logtoConsoleUrl).href)
);
await expect(page).toMatchElement(
'div[class$=main] div[class$=container] div[class$=cardTitle] div[class$=titleEllipsis]',
{
text: 'Sign-in experience',
}
);
// Start & finish guide
await expect(page).toClick('div[class$=container] div[class$=content] button span', {
text: 'Get Started',
});
await expect(page).toClick(
'div[class$=ReactModalPortal] div[class$=footerContent] > button span',
{
text: 'Done',
}
);
// Land on branding tab by default
expect(page.url()).toBe(new URL(`console/sign-in-experience/branding`, logtoConsoleUrl).href);
// Wait for the branding tab to load
await expect(page).toMatchElement('div[class$=tabContent] div[class$=card] div[class$=title]', {
text: 'BRANDING AREA',
});
await expect(page).toMatchElement('div[class$=tabContent] div[class$=card] div[class$=title]', {
text: 'Custom CSS',
});
});
describe('update branding config', () => {
it('update branding config', async () => {
// Enabled dark mode
await expect(page).toClick(
'form div[class$=field] label[class$=switch]:has(input[name="color.isDarkModeEnabled"])'
);
// Update brand color
const brandColorField = await expect(page).toMatchElement(
'div[class$=field]:has(div[class$=headline] div[class$=title])',
{
text: 'Brand color',
}
);
await expect(brandColorField).toClick('div[role=button]');
await expect(page).toFill('input[id^=rc-editable-input]', testPrimaryColor);
// Close the color input
await page.keyboard.press('Escape');
// Recalculate dark brand color
await expect(page).toClick('div[class$=darkModeTip] button span', { text: 'Recalculate' });
// Wait for the recalculate to finish
await page.waitForTimeout(500);
// Fill in the custom CSS
await expect(page).toFill(
'div[class$=editor] textarea',
'body { background-color: #5B4D8E; }'
);
await trySaveChanges(page);
await waitForToaster(page, {
text: 'Saved',
});
});
it('reset branding config', async () => {
// Reset branding config
const brandColorField = await expect(page).toMatchElement(
'div[class$=field]:has(div[class$=headline] div[class$=title])',
{
text: 'Brand color',
}
);
await expect(brandColorField).toClick('div[role=button]');
await expect(page).toFill('input[id^=rc-editable-input]', defaultPrimaryColor);
// Close the color input
await page.keyboard.press('Escape');
// Recalculate dark brand color
await expect(page).toClick('div[class$=darkModeTip] button span', { text: 'Recalculate' });
// Wait for the recalculate to finish
await page.waitForTimeout(500);
// Fill in the custom CSS
await expect(page).toFill('div[class$=editor] textarea', '');
await trySaveChanges(page);
await waitForToaster(page, {
text: 'Saved',
});
// Disable dark mode
await expect(page).toClick(
'form div[class$=field] label[class$=switch]:has(input[name="color.isDarkModeEnabled"])'
);
await trySaveChanges(page);
await waitForToaster(page, {
text: 'Saved',
});
});
});
});

View file

@ -0,0 +1,100 @@
import { logtoConsoleUrl as logtoConsoleUrlString } from '#src/constants.js';
import { goToAdminConsole } from '#src/ui-helpers/index.js';
import { expectNavigation, appendPathname } from '#src/utils.js';
import { waitForFormCard, expectToSelectColor, expectToSaveSignInExperience } from './helpers.js';
const defaultPrimaryColor = '#6139F6';
const testPrimaryColor = '#5B4D8E';
await page.setViewport({ width: 1920, height: 1080 });
describe('sign-in experience: branding', () => {
const logtoConsoleUrl = new URL(logtoConsoleUrlString);
beforeAll(async () => {
await goToAdminConsole();
});
it('navigate to sign-in experience page', async () => {
await expectNavigation(
page.goto(appendPathname('/console/sign-in-experience', logtoConsoleUrl).href)
);
await expect(page).toMatchElement(
'div[class$=main] div[class$=container] div[class$=cardTitle] div[class$=titleEllipsis]',
{
text: 'Sign-in experience',
}
);
// Start & finish guide
await expect(page).toClick('div[class$=container] div[class$=content] button span', {
text: 'Get Started',
});
await expect(page).toClick(
'div[class$=ReactModalPortal] div[class$=footerContent] > button span',
{
text: 'Done',
}
);
// Land on branding tab by default
expect(page.url()).toBe(new URL(`console/sign-in-experience/branding`, logtoConsoleUrl).href);
// Wait for the branding tab to load
await waitForFormCard(page, 'BRANDING AREA');
await waitForFormCard(page, 'Custom CSS');
});
it('update branding config', async () => {
// Enabled dark mode
await expect(page).toClick(
'form div[class$=field] label[class$=switch]:has(input[name="color.isDarkModeEnabled"])'
);
// Update brand color
await expectToSelectColor(page, {
field: 'Brand color',
color: testPrimaryColor,
});
// Recalculate dark brand color
await expect(page).toClick('div[class$=darkModeTip] button span', { text: 'Recalculate' });
// Wait for the recalculate to finish
await page.waitForTimeout(500);
// Fill in the custom CSS
await expect(page).toFill('div[class$=editor] textarea', 'body { background-color: #5B4D8E; }');
await expectToSaveSignInExperience(page);
});
it('reset branding config', async () => {
// Reset branding config
await expectToSelectColor(page, {
field: 'Brand color',
color: defaultPrimaryColor,
});
// Recalculate dark brand color
await expect(page).toClick('div[class$=darkModeTip] button span', { text: 'Recalculate' });
// Wait for the recalculate to finish
await page.waitForTimeout(500);
// Fill in the custom CSS
await expect(page).toFill('div[class$=editor] textarea', '');
await expectToSaveSignInExperience(page);
// Disable dark mode
await expect(page).toClick(
'form div[class$=field] label[class$=switch]:has(input[name="color.isDarkModeEnabled"])'
);
await expectToSaveSignInExperience(page);
});
});

View file

@ -0,0 +1,58 @@
import { type Page } from 'puppeteer';
import { trySaveChanges, expectConfirmModalAndAct, waitForToaster } from '#src/ui-helpers/index.js';
export const waitForFormCard = async (page: Page, title: string) => {
await expect(page).toMatchElement('div[class$=tabContent] div[class$=card] div[class$=title]', {
text: title,
});
};
type ExpectToSelectColorOptions = {
field: string;
color: string;
};
export const expectToSelectColor = async (
page: Page,
{ field, color }: ExpectToSelectColorOptions
) => {
const colorField = await expect(page).toMatchElement(
'div[class$=field]:has(div[class$=headline] div[class$=title])',
{
text: field,
}
);
await expect(colorField).toClick('div[role=button]');
await expect(page).toFill('input[id^=rc-editable-input]', color);
// Close the color input
await page.keyboard.press('Escape');
};
type ExpectToSaveSignInExperienceOptions = {
needToConfirmChanges?: boolean;
};
export const expectToSaveSignInExperience = async (
page: Page,
options?: ExpectToSaveSignInExperienceOptions
) => {
const { needToConfirmChanges = false } = options ?? {};
await trySaveChanges(page);
if (needToConfirmChanges) {
// Confirm changes
await expectConfirmModalAndAct(page, {
title: 'Reminder',
actionText: 'Confirm',
});
}
await waitForToaster(page, {
text: 'Saved',
});
};

View file

@ -0,0 +1,161 @@
import { ConnectorType } from '@logto/schemas';
import { type Page } from 'puppeteer';
import { logtoConsoleUrl as logtoConsoleUrlString } from '#src/constants.js';
import { expectToClickDetailsPageOption, waitForToaster } from '#src/ui-helpers/index.js';
import { expectNavigation, appendPathname } from '#src/utils.js';
import {
expectToConfirmConnectorDeletion,
expectToSelectConnector,
waitForConnectorCreationGuide,
} from '../../connectors/helpers.js';
const logtoConsoleUrl = new URL(logtoConsoleUrlString);
type TestConnector = {
factoryId: string;
name: string;
connectorType: ConnectorType;
data: Record<string, string>;
};
export const testSendgridConnector: TestConnector = {
factoryId: 'sendgrid-email-service',
name: 'SendGrid Email',
connectorType: ConnectorType.Email,
data: {
'formConfig.apiKey': 'api-key',
'formConfig.fromEmail': 'foo@example.com',
'formConfig.fromName': 'Logto',
},
};
export const testTwilioConnector: TestConnector = {
factoryId: 'twilio-short-message-service',
name: 'Twilio SMS Service',
connectorType: ConnectorType.Sms,
data: {
'formConfig.accountSID': 'account-sid',
'formConfig.authToken': 'auth-token',
'formConfig.fromMessagingServiceSID': 'from-messaging-service-sid',
},
};
export const testAppleConnector: TestConnector = {
factoryId: 'apple-universal',
name: 'Apple',
connectorType: ConnectorType.Social,
data: {
'formConfig.clientId': 'client-id',
},
};
export const expectToSetupPasswordlessConnector = async (
page: Page,
{ factoryId, name, connectorType, data }: TestConnector
) => {
if (connectorType === ConnectorType.Social) {
return;
}
await expectNavigation(
page.goto(appendPathname('/console/connectors/passwordless', logtoConsoleUrl).href)
);
const connectorItem = await expect(page).toMatchElement(
'div[class$=item] div[class$=previewTitle]:has(>div)',
{
text: connectorType === ConnectorType.Email ? 'Email connector' : 'SMS connector',
}
);
const setupConnectorButton = await expect(connectorItem).toMatchElement('button span', {
text: 'Set Up',
});
await setupConnectorButton.click();
await setupConnectorButton.click();
await expectToSelectConnector(page, {
factoryId,
connectorType,
});
await waitForConnectorCreationGuide(page, name);
await expect(page).toFillForm('.ReactModalPortal form', data);
await expect(page).toClick('.ReactModalPortal form div[class$=footer] button[type=submit] span', {
text: 'Save and Done',
});
await waitForToaster(page, { text: 'Saved' });
};
export const expectToSetupSocialConnector = async (
page: Page,
{ factoryId, name, connectorType, data }: TestConnector
) => {
if (connectorType !== ConnectorType.Social) {
return;
}
await expectNavigation(
page.goto(appendPathname('/console/connectors/social', logtoConsoleUrl).href)
);
await expect(page).toClick('div[class$=headline] button[class$=withIcon] span', {
text: 'Add Social Connector',
});
await expectToSelectConnector(page, {
factoryId,
connectorType,
});
await waitForConnectorCreationGuide(page, name);
await expect(page).toFillForm('.ReactModalPortal form', data);
await expect(page).toClick('.ReactModalPortal form div[class$=footer] button[type=submit] span', {
text: 'Save and Done',
});
await waitForToaster(page, { text: 'Saved' });
};
export const expectToDeletePasswordlessConnector = async (page: Page, { name }: TestConnector) => {
await expectNavigation(
page.goto(appendPathname('/console/connectors/passwordless', logtoConsoleUrl).href)
);
await expect(page).toClick('table tbody tr td div[class$=item] a[class$=title] span', {
text: name,
});
await expect(page).toMatchElement('div[class$=header] div[class$=name] span', {
text: name,
});
await expectToClickDetailsPageOption(page, 'Delete');
await expectToConfirmConnectorDeletion(page);
};
export const expectToDeleteSocialConnector = async (page: Page, { name }: TestConnector) => {
await expectNavigation(
page.goto(appendPathname('/console/connectors/social', logtoConsoleUrl).href)
);
await expect(page).toClick('table tbody tr td div[class$=item] a[class$=title] span', {
text: name,
});
await expect(page).toMatchElement('div[class$=header] div[class$=name] span', {
text: name,
});
await expectToClickDetailsPageOption(page, 'Delete');
await expectToConfirmConnectorDeletion(page);
};

View file

@ -0,0 +1,577 @@
import { logtoConsoleUrl as logtoConsoleUrlString } from '#src/constants.js';
import { expectToClickNavTab, goToAdminConsole } from '#src/ui-helpers/index.js';
import { expectNavigation, appendPathname } from '#src/utils.js';
import { expectToSaveSignInExperience, waitForFormCard } from '../helpers.js';
import {
expectToDeletePasswordlessConnector,
expectToSetupPasswordlessConnector,
testSendgridConnector,
testTwilioConnector,
expectToSetupSocialConnector,
testAppleConnector,
expectToDeleteSocialConnector,
} from './connector-setup-helpers.js';
import {
expectToAddSignInMethod,
expectToAddSocialSignInConnector,
expectToClickSignInMethodAuthnOption,
expectToClickSignUpAuthnOption,
expectToRemoveSignInMethod,
expectToRemoveSocialSignInConnector,
expectToResetSignUpAndSignInConfig,
expectToSelectSignUpIdentifier,
expectToSwapSignInMethodAuthnOption,
} from './helpers.js';
await page.setViewport({ width: 1920, height: 1080 });
describe('sign-in experience(happy path): sign-up and sign-in', () => {
const logtoConsoleUrl = new URL(logtoConsoleUrlString);
beforeAll(async () => {
await goToAdminConsole();
// Email connector
await expectToSetupPasswordlessConnector(page, testSendgridConnector);
// SMS connector
await expectToSetupPasswordlessConnector(page, testTwilioConnector);
// Social connector
await expectToSetupSocialConnector(page, testAppleConnector);
});
afterAll(async () => {
await expectToDeletePasswordlessConnector(page, testSendgridConnector);
await expectToDeletePasswordlessConnector(page, testTwilioConnector);
await expectToDeleteSocialConnector(page, testAppleConnector);
});
it('navigate to sign-in experience page', async () => {
await expectNavigation(
page.goto(appendPathname('/console/sign-in-experience', logtoConsoleUrl).href)
);
// Land on branding tab by default
expect(page.url()).toBe(new URL(`console/sign-in-experience/branding`, logtoConsoleUrl).href);
});
it('navigate to sign-up and sign-in tab', async () => {
await expectToClickNavTab(page, 'Sign-up and Sign-in');
await waitForFormCard(page, 'SIGN UP');
await waitForFormCard(page, 'SIGN IN');
await waitForFormCard(page, 'SOCIAL SIGN-IN');
});
describe('email as sign-up identifier (verify only)', () => {
afterAll(async () => {
await expectToResetSignUpAndSignInConfig(page);
});
it('select email as sign-in method and disable password settings for sign-up', async () => {
await expectToSelectSignUpIdentifier(page, 'Email address');
// Disable password settings for sign-up
await expectToClickSignUpAuthnOption(page, 'Create your password');
// Username will be added in later tests
await expectToRemoveSignInMethod(page, 'Username');
/**
* Sign-in method
* - Email address: password + verification code
*/
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
it('update email sign-in method', async () => {
/**
* Sign-in method
* - Email address: verification code
*/
await expectToClickSignInMethodAuthnOption(page, {
method: 'Email address',
option: 'Password',
});
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Email address: verification code + password
*/
await expectToClickSignInMethodAuthnOption(page, {
method: 'Email address',
option: 'Password',
});
await expectToSwapSignInMethodAuthnOption(page, 'Email address');
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
it('add username sign-in method', async () => {
/**
* Sign-in method
* - Email address: verification code + password
* - Username: password
*/
await expectToAddSignInMethod(page, 'Username');
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
it('add & update phone number sign-in method', async () => {
await expectToAddSignInMethod(page, 'Phone number');
/**
* Sign-in method
* - Email address: verification code + password
* - Username: password
* - Phone number: password + verification code
*/
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Email address: verification code + password
* - Username: password
* - Phone number: verification code
*/
await expectToClickSignInMethodAuthnOption(page, {
method: 'Phone number',
option: 'Password',
});
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Email address: verification code + password
* - Phone number: verification code
*/
await expectToRemoveSignInMethod(page, 'Username');
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Email address: verification code + password
* - Phone number: password + verification code
*/
await expectToClickSignInMethodAuthnOption(page, {
method: 'Phone number',
option: 'Password',
});
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
});
describe('email as sign-up identifier (password & verify)', () => {
afterAll(async () => {
await expectToResetSignUpAndSignInConfig(page);
});
it('select email as sign-in method and enable password settings for sign-up', async () => {
await expectToSelectSignUpIdentifier(page, 'Email address');
// Username will be added in later tests
await expectToRemoveSignInMethod(page, 'Username');
/**
* Sign-in method
* - Email address: password + verification code
*/
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
it('update email sign-in method', async () => {
/**
* Sign-in method
* - Email address: verification code + password
*/
// Sign-in method: Email address + verification code + password
await expectToSwapSignInMethodAuthnOption(page, 'Email address');
await expectToSaveSignInExperience(page);
/**
* Sign-in method
* - Email address: password
*/
await expectToClickSignInMethodAuthnOption(page, {
method: 'Email address',
option: 'Verification code',
});
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
it('add phone number & username as sign-in method', async () => {
/**
* Sign-in method
* - Email address: password
* - Phone number: password + verification code
*/
await expectToAddSignInMethod(page, 'Phone number');
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Email address: password
* - Phone number: password
*/
await expectToClickSignInMethodAuthnOption(page, {
method: 'Phone number',
option: 'Verification code',
});
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Email address: password
* - Phone number: password
* - Username: password
*/
await expectToAddSignInMethod(page, 'Username');
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Email address: password
* - Phone number: password + verification code
* - Username: password
*/
await expectToClickSignInMethodAuthnOption(page, {
method: 'Phone number',
option: 'Verification code',
});
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
});
describe('phone as sign-up identifier (verify only)', () => {
afterAll(async () => {
await expectToResetSignUpAndSignInConfig(page);
});
it('select email as sign-in method and disable password settings for sign-up', async () => {
await expectToSelectSignUpIdentifier(page, 'Phone number');
// Disable password settings for sign-up
await expectToClickSignUpAuthnOption(page, 'Create your password');
// Username will be added in later tests
await expectToRemoveSignInMethod(page, 'Username');
/**
* Sign-in method
* - Phone number: password + verification code
*/
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
it('update sign-in methods', async () => {
/**
* Sign-in method
* - Phone number: verification code + password
*/
await expectToSwapSignInMethodAuthnOption(page, 'Phone number');
await expectToSaveSignInExperience(page);
/**
* Sign-in method
* - Phone number: verification code
*/
await expectToClickSignInMethodAuthnOption(page, {
method: 'Phone number',
option: 'Password',
});
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Phone number: verification code
* - Email address: password + verification code
*/
await expectToAddSignInMethod(page, 'Email address');
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Phone number: verification code
* - Email address: verification code
*/
await expectToClickSignInMethodAuthnOption(page, {
method: 'Email address',
option: 'Password',
});
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Phone number: verification code
* - Email address: verification code
* - Username: password
*/
await expectToAddSignInMethod(page, 'Username');
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
});
describe('phone as sign-up identifier (password & verify)', () => {
afterAll(async () => {
await expectToResetSignUpAndSignInConfig(page);
});
it('select email as sign-in method and enable password settings for sign-up', async () => {
await expectToSelectSignUpIdentifier(page, 'Phone number');
// Username will be added in later tests
await expectToRemoveSignInMethod(page, 'Username');
/**
* Sign-in method
* - Phone number: password + verification code
*/
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
it('update sign-in methods', async () => {
/**
* Sign-in method
* - Phone number: verification code + password
*/
await expectToSwapSignInMethodAuthnOption(page, 'Phone number');
await expectToSaveSignInExperience(page);
/**
* Sign-in method
* - Phone number: password
*/
await expectToClickSignInMethodAuthnOption(page, {
method: 'Phone number',
option: 'Verification code',
});
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Phone number: password
* - Username: password
*/
await expectToAddSignInMethod(page, 'Username');
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
});
describe('email or phone as sign-up identifier (verify only)', () => {
afterAll(async () => {
await expectToResetSignUpAndSignInConfig(page);
});
it('select email or phone as sign-up identifier and disable password settings for sign-up', async () => {
await expectToSelectSignUpIdentifier(page, 'Email address or phone number');
await expectToClickSignUpAuthnOption(page, 'Create your password');
// Username will be added in later tests
await expectToRemoveSignInMethod(page, 'Username');
/**
* Sign-in method
* - Email address: password + verification code
* - Phone number: password + verification code
*/
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
it('update sign-in method configs', async () => {
/**
* Sign-in method
* - Email address: verification code + password
* - Phone number: verification code + password
*/
await expectToSwapSignInMethodAuthnOption(page, 'Email address');
await expectToSwapSignInMethodAuthnOption(page, 'Phone number');
await expectToSaveSignInExperience(page);
/**
* Sign-in method
* - Email address: verification code
* - Phone number: verification code + password
*/
await expectToClickSignInMethodAuthnOption(page, {
method: 'Email address',
option: 'Password',
});
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Email address: verification code
* - Phone number: verification code
*/
await expectToClickSignInMethodAuthnOption(page, {
method: 'Phone number',
option: 'Password',
});
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Email address: verification code
* - Phone number: verification code
* - Username: password
*/
await expectToAddSignInMethod(page, 'Username');
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
});
describe('email or phone as sign-up identifier (password & verify)', () => {
afterAll(async () => {
await expectToResetSignUpAndSignInConfig(page);
});
it('select email or phone as sign-up identifier and enable password settings for sign-up', async () => {
await expectToSelectSignUpIdentifier(page, 'Email address or phone number');
// Username will be added in later tests
await expectToRemoveSignInMethod(page, 'Username');
/**
* Sign-in method
* - Email address: password + verification code
* - Phone number: password + verification code
*/
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
it('update sign-in method configs', async () => {
/**
* Sign-in method
* - Email address: verification code + password
* - Phone number: verification code + password
*/
await expectToSwapSignInMethodAuthnOption(page, 'Email address');
await expectToSwapSignInMethodAuthnOption(page, 'Phone number');
await expectToSaveSignInExperience(page);
/**
* Sign-in method
* - Email address: password
* - Phone number: verification code + password
*/
await expectToClickSignInMethodAuthnOption(page, {
method: 'Email address',
option: 'Verification code',
});
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Email address: password
* - Phone number: password
*/
await expectToClickSignInMethodAuthnOption(page, {
method: 'Phone number',
option: 'Verification code',
});
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Email address: password
* - Phone number: password
* - Username: password
*/
await expectToAddSignInMethod(page, 'Username');
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
});
describe('not applicable as sign-up identifier', () => {
afterAll(async () => {
await expectToResetSignUpAndSignInConfig(page);
});
it('select not applicable as sign-up identifier', async () => {
await expectToSelectSignUpIdentifier(page, 'Not applicable');
await expectToRemoveSignInMethod(page, 'Username');
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
it('update sign-in methods', async () => {
/**
* Sign-in method
* - Email address: password + verification code
*/
await expectToAddSignInMethod(page, 'Email address', false);
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Email address: password
*/
await expectToClickSignInMethodAuthnOption(page, {
method: 'Email address',
option: 'Verification code',
});
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Email address: verification code
*/
await expectToClickSignInMethodAuthnOption(page, {
method: 'Email address',
option: 'Verification code',
});
await expectToClickSignInMethodAuthnOption(page, {
method: 'Email address',
option: 'Password',
});
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Email address: verification code
* - Phone number: password verification code
*/
await expectToAddSignInMethod(page, 'Phone number');
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Email address: verification code
* - Phone number: password
*/
await expectToClickSignInMethodAuthnOption(page, {
method: 'Phone number',
option: 'Verification code',
});
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
/**
* Sign-in method
* - Email address: verification code
* - Phone number: password
* - Username: password
*/
await expectToAddSignInMethod(page, 'Username');
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
it('add social sign-in connector', async () => {
await expectToAddSocialSignInConnector(page, 'Apple');
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
// Reset
await expectToRemoveSocialSignInConnector(page, 'Apple');
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
});
describe('disable user registration', () => {
it('navigate to others tab', async () => {
await expectToClickNavTab(page, 'Others');
await waitForFormCard(page, 'TERMS');
await waitForFormCard(page, 'LANGUAGES');
await waitForFormCard(page, 'ADVANCED OPTIONS');
});
it('disable user registration', async () => {
const switchSelector = 'label[class$=switch]:has(input[name=createAccountEnabled])';
await expect(page).toClick(switchSelector);
await expectToSaveSignInExperience(page);
// Reset
await expect(page).toClick(switchSelector);
await expectToSaveSignInExperience(page);
});
});
});

View file

@ -0,0 +1,240 @@
import { type Page } from 'puppeteer';
import { expectToSaveSignInExperience } from '../helpers.js';
export const expectToSelectSignUpIdentifier = async (page: Page, identifier: string) => {
const signUpIdentifierField = await expect(page).toMatchElement(
'div[class$=field]:has(div[class$=headline] > div[class$=title])',
{
text: 'Sign-up identifier',
}
);
await expect(signUpIdentifierField).toClick('div[role=button][class*=select]');
// Wait for the dropdown to be rendered in the correct position
await page.waitForTimeout(500);
await expect(page).toClick(
'.ReactModalPortal div[class$=dropdownContainer] div[role=menuitem] div',
{
text: identifier,
}
);
await page.waitForSelector('.ReactModalPortal div[class$=dropdownContainer]', {
hidden: true,
});
await expect(signUpIdentifierField).toMatchElement('div[class*=select] div[class$=title] div', {
text: identifier,
});
// Wait for the config to update
await page.waitForTimeout(500);
};
export const expectToClickSignUpAuthnOption = async (page: Page, option: string) => {
const signUpAuthnSettingsFiled = await expect(page).toMatchElement(
'div[class$=field]:has(div[class$=headline] > div[class$=title])',
{
text: 'Authentication setting for sign-up',
}
);
await expect(signUpAuthnSettingsFiled).toClick('div[class$=selections] span[class$=label]', {
text: option,
});
};
export const expectToAddSignInMethod = async (page: Page, method: string, isAddAnother = true) => {
const signInMethodsField = await expect(page).toMatchElement(
'div[class$=field]:has(div[class$=headline] > div[class$=title])',
{
text: 'Identifier and authentication settings for sign-in',
}
);
// Click Add another
await expect(signInMethodsField).toClick('button span', {
text: isAddAnother ? 'Add Another' : 'Add Sign-in Method',
});
// Wait for the dropdown to be rendered in the correct position
await page.waitForTimeout(500);
await expect(page).toClick('.ReactModalPortal div[class$=dropdownContainer] div[role=menuitem]', {
text: method,
});
await page.waitForSelector('.ReactModalPortal div[class$=dropdownContainer]', {
hidden: true,
});
};
type ExpectSignInMethodAuthnOptionOptions = {
method: string;
option: string;
};
export const expectToClickSignInMethodAuthnOption = async (
page: Page,
{ method, option }: ExpectSignInMethodAuthnOptionOptions
) => {
const methodItem = await expect(page).toMatchElement(
'div[class$=signInMethodItem]:has(div[class$=identifier])',
{
text: method,
}
);
await expect(methodItem).toClick('div[class*=authentication] span[class$=label]', {
text: option,
});
// Wait for the config to update
await page.waitForTimeout(500);
};
export const expectToSwapSignInMethodAuthnOption = async (page: Page, method: string) => {
const methodItem = await expect(page).toMatchElement(
'div[class$=signInMethodItem]:has(div[class$=identifier])',
{
text: method,
}
);
await expect(methodItem).toClick('div[class*=authentication] div[class$=swapButton] button');
};
export const expectToRemoveSignInMethod = async (page: Page, method: string) => {
const methodItem = await expect(page).toMatchElement(
'div[class$=signInMethodItem]:has(div[class$=identifier])',
{
text: method,
}
);
await expect(methodItem).toClick('div[class$=anchor] button:last-of-type');
// Wait for the config to update
await page.waitForTimeout(500);
};
export const expectSignInMethodError = async (page: Page, method: string) => {
await expect(page).toMatchElement(
'div[class$=signInMethodItem] div[class$=error] div[class$=identifier]',
{
text: method,
}
);
};
type ExpectNotificationOnFiledOptions = {
field: string;
content?: RegExp | string;
};
export const expectNotificationInFiled = async (
page: Page,
{ field, content }: ExpectNotificationOnFiledOptions
) => {
const signInMethodsField = await expect(page).toMatchElement(
'div[class$=field]:has(div[class$=headline] > div[class$=title])',
{
text: field,
}
);
await expect(signInMethodsField).toMatchElement(
'div[class*=inlineNotification] div[class$=content]',
{
text: content,
}
);
};
export const expectSignUpIdentifierSelectorError = async (page: Page) => {
const signUpIdentifierField = await expect(page).toMatchElement(
'div[class$=field]:has(div[class$=headline] > div[class$=title])',
{
text: 'Sign-up identifier',
}
);
await expect(signUpIdentifierField).toMatchElement('div[class*=select][class*=error]');
};
export const expectToResetSignUpAndSignInConfig = async (page: Page, needSave = true) => {
// Select 'Email address or phone number' first to ensure the sign-in method contains phone and email
await expectToSelectSignUpIdentifier(page, 'Email address or phone number');
await expectToSelectSignUpIdentifier(page, 'Username');
await expectToRemoveSignInMethod(page, 'Email address');
await expectToRemoveSignInMethod(page, 'Phone number');
if (needSave) {
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
}
};
export const expectToAddSocialSignInConnector = async (page: Page, name: string) => {
const socialSignInField = await expect(page).toMatchElement(
'div[class$=field]:has(div[class$=headline] > div[class$=title])',
{
text: 'Social sign-in',
}
);
await expect(socialSignInField).toClick('button span', {
text: 'Add Social Connector',
});
// Wait for the dropdown to be rendered in the correct position
await page.waitForTimeout(500);
await expect(page).toClick(
'.ReactModalPortal div[class$=dropdownContainer] div[role=menuitem] span[class$=name]',
{
text: name,
}
);
await page.waitForSelector('.ReactModalPortal div[class$=dropdownContainer]', {
hidden: true,
});
};
export const expectToRemoveSocialSignInConnector = async (page: Page, name: string) => {
const socialSignInField = await expect(page).toMatchElement(
'div[class$=field]:has(div[class$=headline] > div[class$=title])',
{
text: 'Social sign-in',
}
);
const connectorItem = await expect(socialSignInField).toMatchElement(
'div[class$=item]:has(span[class$=name])',
{
text: name,
}
);
await expect(connectorItem).toClick('button:last-of-type');
};
type ExpectErrorsOnNavTabOptions = {
tab: string;
error?: RegExp | string;
};
export const expectErrorsOnNavTab = async (
page: Page,
{ tab, error }: ExpectErrorsOnNavTabOptions
) => {
const signUpAndSignInTab = await expect(page).toMatchElement('nav div[class$=item]:has(a)', {
text: tab,
});
await expect(signUpAndSignInTab).toMatchElement('div[class$=errors]', {
text: error,
});
};

View file

@ -0,0 +1,301 @@
import { logtoConsoleUrl as logtoConsoleUrlString } from '#src/constants.js';
import { expectToClickNavTab, goToAdminConsole, trySaveChanges } from '#src/ui-helpers/index.js';
import { expectNavigation, appendPathname } from '#src/utils.js';
import { expectToSaveSignInExperience, waitForFormCard } from '../helpers.js';
import {
expectToDeletePasswordlessConnector,
expectToSetupPasswordlessConnector,
testSendgridConnector,
testTwilioConnector,
} from './connector-setup-helpers.js';
import {
expectToSelectSignUpIdentifier,
expectNotificationInFiled,
expectSignUpIdentifierSelectorError,
expectToAddSignInMethod,
expectSignInMethodError,
expectErrorsOnNavTab,
expectToClickSignUpAuthnOption,
expectToClickSignInMethodAuthnOption,
expectToResetSignUpAndSignInConfig,
} from './helpers.js';
describe('sign-in experience(sad path): sign-up and sign-in', () => {
const logtoConsoleUrl = new URL(logtoConsoleUrlString);
beforeAll(async () => {
await goToAdminConsole();
});
it('navigate to sign-in experience page', async () => {
await expectNavigation(
page.goto(appendPathname('/console/sign-in-experience', logtoConsoleUrl).href)
);
// Land on branding tab by default
expect(page.url()).toBe(new URL(`console/sign-in-experience/branding`, logtoConsoleUrl).href);
});
it('navigate to sign-up and sign-in tab', async () => {
await expectToClickNavTab(page, 'Sign-up and Sign-in');
await waitForFormCard(page, 'SIGN UP');
await waitForFormCard(page, 'SIGN IN');
await waitForFormCard(page, 'SOCIAL SIGN-IN');
});
describe('cases that no connector is setup', () => {
describe('email address as sign-up identifier', () => {
afterAll(async () => {
await expectToResetSignUpAndSignInConfig(page, false);
});
it('should fail to setup email as sign-up identifier', async () => {
await expectToSelectSignUpIdentifier(page, 'Email address');
// Disable password settings for sign-up settings
await expectToClickSignUpAuthnOption(page, 'Create your password');
await expectNotificationInFiled(page, {
field: 'Sign-up identifier',
content: /No email connector set-up yet./,
});
await trySaveChanges(page);
await expectSignUpIdentifierSelectorError(page);
await expectErrorsOnNavTab(page, {
tab: 'Sign-up and Sign-in',
error: '1 errors',
});
});
it('should fail to add phone number sign-in method', async () => {
await expectToAddSignInMethod(page, 'Phone number');
await expectNotificationInFiled(page, {
field: 'Identifier and authentication settings for sign-in',
content: /No SMS connector set-up yet./,
});
await trySaveChanges(page);
await expectSignInMethodError(page, 'Phone number');
await expectErrorsOnNavTab(page, {
tab: 'Sign-up and Sign-in',
error: '2 errors',
});
// Disable password option for sign-in method
await expectToClickSignInMethodAuthnOption(page, {
method: 'Phone number',
option: 'Password',
});
await trySaveChanges(page);
await expectSignInMethodError(page, 'Phone number');
await expectErrorsOnNavTab(page, {
tab: 'Sign-up and Sign-in',
error: '2 errors',
});
});
});
describe('phone number as sign-up identifier', () => {
afterAll(async () => {
await expectToResetSignUpAndSignInConfig(page, false);
});
it('should fail to setup phone number as sign-up identifier', async () => {
await expectToSelectSignUpIdentifier(page, 'Phone number');
// Disable password settings for sign-up settings
await expectToClickSignUpAuthnOption(page, 'Create your password');
await expectNotificationInFiled(page, {
field: 'Sign-up identifier',
content: /No SMS connector set-up yet./,
});
await trySaveChanges(page);
await expectSignUpIdentifierSelectorError(page);
await expectErrorsOnNavTab(page, {
tab: 'Sign-up and Sign-in',
error: '1 errors',
});
});
it('should fail to add email address sign-in method', async () => {
await expectToAddSignInMethod(page, 'Email address');
await expectNotificationInFiled(page, {
field: 'Identifier and authentication settings for sign-in',
content: /No email connector set-up yet./,
});
await trySaveChanges(page);
await expectSignInMethodError(page, 'Email address');
await expectErrorsOnNavTab(page, {
tab: 'Sign-up and Sign-in',
error: '2 errors',
});
// Disable password option for sign-in method
await expectToClickSignInMethodAuthnOption(page, {
method: 'Email address',
option: 'Password',
});
await trySaveChanges(page);
await expectSignInMethodError(page, 'Email address');
await expectErrorsOnNavTab(page, {
tab: 'Sign-up and Sign-in',
error: '2 errors',
});
});
});
describe('social sign-in', () => {
it('should display no social connector notification in social sign-in field', async () => {
await expectNotificationInFiled(page, {
field: 'Social sign-in',
content: /No social connector set-up yet./,
});
});
});
});
describe('cases that only Email connector is setup', () => {
beforeAll(async () => {
// Email connector
await expectToSetupPasswordlessConnector(page, testSendgridConnector);
// Nav back to sign-in experience page
await expectNavigation(
page.goto(
appendPathname('/console/sign-in-experience/sign-up-and-sign-in', logtoConsoleUrl).href
)
);
});
afterAll(async () => {
await expectToDeletePasswordlessConnector(page, testSendgridConnector);
await expectNavigation(
page.goto(
appendPathname('/console/sign-in-experience/sign-up-and-sign-in', logtoConsoleUrl).href
)
);
});
describe('email address as sign-up identifier', () => {
afterAll(async () => {
await expectToResetSignUpAndSignInConfig(page);
});
it('should setup email as sign-up identifier', async () => {
await expectToSelectSignUpIdentifier(page, 'Email address');
// Disable password settings for sign-up settings
await expectToClickSignUpAuthnOption(page, 'Create your password');
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
it('should fail to add phone number sign-in method', async () => {
await expectToAddSignInMethod(page, 'Phone number');
await expectNotificationInFiled(page, {
field: 'Identifier and authentication settings for sign-in',
content: /No SMS connector set-up yet./,
});
await trySaveChanges(page);
await expectSignInMethodError(page, 'Phone number');
await expectErrorsOnNavTab(page, {
tab: 'Sign-up and Sign-in',
error: '1 errors',
});
// Disable password option for sign-in method
await expectToClickSignInMethodAuthnOption(page, {
method: 'Phone number',
option: 'Password',
});
await trySaveChanges(page);
await expectSignInMethodError(page, 'Phone number');
await expectErrorsOnNavTab(page, {
tab: 'Sign-up and Sign-in',
error: '1 errors',
});
});
});
});
describe('cases that only SMS connector is setup', () => {
beforeAll(async () => {
// SMS connector
await expectToSetupPasswordlessConnector(page, testTwilioConnector);
// Nav back to sign-in experience page
await expectNavigation(
page.goto(
appendPathname('/console/sign-in-experience/sign-up-and-sign-in', logtoConsoleUrl).href
)
);
});
afterAll(async () => {
await expectToDeletePasswordlessConnector(page, testTwilioConnector);
await expectNavigation(
page.goto(
appendPathname('/console/sign-in-experience/sign-up-and-sign-in', logtoConsoleUrl).href
)
);
});
describe('phone number as sign-up identifier', () => {
afterAll(async () => {
await expectToResetSignUpAndSignInConfig(page);
});
it('should setup phone number as sign-up identifier', async () => {
await expectToSelectSignUpIdentifier(page, 'Phone number');
// Disable password settings for sign-up settings
await expectToClickSignUpAuthnOption(page, 'Create your password');
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
});
it('should fail to add email sign-in method', async () => {
await expectToAddSignInMethod(page, 'Email address');
await expectNotificationInFiled(page, {
field: 'Identifier and authentication settings for sign-in',
content: /No email connector set-up yet./,
});
await trySaveChanges(page);
await expectSignInMethodError(page, 'Email address');
await expectErrorsOnNavTab(page, {
tab: 'Sign-up and Sign-in',
error: '1 errors',
});
// Disable password option for sign-in method
await expectToClickSignInMethodAuthnOption(page, {
method: 'Email address',
option: 'Password',
});
await trySaveChanges(page);
await expectSignInMethodError(page, 'Email address');
await expectErrorsOnNavTab(page, {
tab: 'Sign-up and Sign-in',
error: '1 errors',
});
});
});
});
});

View file

@ -105,3 +105,9 @@ export const expectConfirmModalAndAct = async (
});
}
};
export const expectToClickNavTab = async (page: Page, tab: string) => {
await expect(page).toClick('nav div[class$=item] div[class$=link] a', {
text: tab,
});
};