Update retries in playwright config and refactor account settings
This update changes the number of retries in the Playwright configuration. Additionally, solid improvements have been made in the account settings, including better data semantics for testing, changes to email confirmation, and adding a new E2E test suite for accounts. The sign-up flow was updated and problems with multi-language support logic were fixed.
This commit is contained in:
@@ -15,8 +15,7 @@ export default defineConfig({
|
|||||||
fullyParallel: true,
|
fullyParallel: true,
|
||||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||||
forbidOnly: !!process.env.CI,
|
forbidOnly: !!process.env.CI,
|
||||||
/* Retry on CI only */
|
retries: process.env.CI ? 3 : 1,
|
||||||
retries: process.env.CI ? 2 : 0,
|
|
||||||
/* Opt out of parallel tests on CI. */
|
/* Opt out of parallel tests on CI. */
|
||||||
workers: process.env.CI ? 1 : undefined,
|
workers: process.env.CI ? 1 : undefined,
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
|
|||||||
35
apps/e2e/tests/account/account.po.ts
Normal file
35
apps/e2e/tests/account/account.po.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { Page } from '@playwright/test';
|
||||||
|
import { AuthPageObject } from '../authentication/auth.po';
|
||||||
|
|
||||||
|
export class AccountPageObject {
|
||||||
|
private readonly page: Page;
|
||||||
|
public auth: AuthPageObject;
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
this.page = page;
|
||||||
|
this.auth = new AuthPageObject(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setup() {
|
||||||
|
return this.auth.signUpFlow('/home/settings');
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateProfileName(name: string) {
|
||||||
|
await this.page.locator('[data-test="update-account-name-form"] input').fill(name);
|
||||||
|
await this.page.locator('[data-test="update-account-name-form"] button').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateProfileEmail(email: string) {
|
||||||
|
await this.page.locator('[data-test="account-email-form-email-input"]').fill(email);
|
||||||
|
await this.page.locator('[data-test="account-email-form-repeat-email-input"]').fill(email);
|
||||||
|
await this.page.locator('[data-test="account-email-form"] button').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
getProfileName() {
|
||||||
|
return this.page.locator('[data-test="account-dropdown-display-name"]');
|
||||||
|
}
|
||||||
|
|
||||||
|
getProfileEmail() {
|
||||||
|
return this.page.locator('[data-test="account-dropdown-email"]');
|
||||||
|
}
|
||||||
|
}
|
||||||
38
apps/e2e/tests/account/account.spec.ts
Normal file
38
apps/e2e/tests/account/account.spec.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { expect, Page, test } from '@playwright/test';
|
||||||
|
import { AccountPageObject } from './account.po';
|
||||||
|
|
||||||
|
test.describe('Account Settings', () => {
|
||||||
|
let page: Page;
|
||||||
|
let account: AccountPageObject;
|
||||||
|
|
||||||
|
test.beforeAll(async ({ browser }) => {
|
||||||
|
page = await browser.newPage();
|
||||||
|
account = new AccountPageObject(page);
|
||||||
|
|
||||||
|
await account.setup();
|
||||||
|
})
|
||||||
|
|
||||||
|
test('user can update their profile name', async () => {
|
||||||
|
const name = 'John Doe';
|
||||||
|
|
||||||
|
await account.updateProfileName(name);
|
||||||
|
|
||||||
|
await page.waitForResponse((resp) => {
|
||||||
|
return resp.url().includes('accounts');
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(account.getProfileName()).toHaveText(name);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user can update their email', async () => {
|
||||||
|
const email = account.auth.createRandomEmail();
|
||||||
|
|
||||||
|
await account.updateProfileEmail(email);
|
||||||
|
|
||||||
|
const req = await page.waitForResponse((resp) => {
|
||||||
|
return resp.url().includes('auth/v1/user');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(req.status()).toBe(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -43,7 +43,7 @@ export class AuthPageObject {
|
|||||||
async visitConfirmEmailLink(email: string) {
|
async visitConfirmEmailLink(email: string) {
|
||||||
await this.page.waitForTimeout(300);
|
await this.page.waitForTimeout(300);
|
||||||
|
|
||||||
return this.mailbox.visitMailbox(email);
|
await this.mailbox.visitMailbox(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
createRandomEmail() {
|
createRandomEmail() {
|
||||||
@@ -51,4 +51,20 @@ export class AuthPageObject {
|
|||||||
|
|
||||||
return `${value.toFixed(0)}@makerkit.dev`;
|
return `${value.toFixed(0)}@makerkit.dev`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async signUpFlow(path: string) {
|
||||||
|
const email = this.createRandomEmail();
|
||||||
|
|
||||||
|
await this.page.goto(`/auth/sign-up?next=${path}`, {
|
||||||
|
waitUntil: 'networkidle',
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.signUp({
|
||||||
|
email,
|
||||||
|
password: 'password',
|
||||||
|
repeatPassword: 'password',
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.visitConfirmEmailLink(email);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,12 @@ export class Mailbox {
|
|||||||
|
|
||||||
const json = await this.getInviteEmail(mailbox);
|
const json = await this.getInviteEmail(mailbox);
|
||||||
|
|
||||||
|
if (!json.body) {
|
||||||
|
console.log(json);
|
||||||
|
|
||||||
|
throw new Error('Email body was not found');
|
||||||
|
}
|
||||||
|
|
||||||
const html = (json.body as { html: string }).html;
|
const html = (json.body as { html: string }).html;
|
||||||
const el = parse(html);
|
const el = parse(html);
|
||||||
|
|
||||||
|
|||||||
@@ -88,9 +88,17 @@ export function PersonalAccountDropdown({
|
|||||||
'fade-in animate-in flex w-full flex-col truncate text-left'
|
'fade-in animate-in flex w-full flex-col truncate text-left'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span className={'truncate text-sm'}>{displayName}</span>
|
<span
|
||||||
|
data-test={'account-dropdown-display-name'}
|
||||||
|
className={'truncate text-sm'}
|
||||||
|
>
|
||||||
|
{displayName}
|
||||||
|
</span>
|
||||||
|
|
||||||
<span className={'text-muted-foreground truncate text-xs'}>
|
<span
|
||||||
|
data-test={'account-dropdown-email'}
|
||||||
|
className={'text-muted-foreground truncate text-xs'}
|
||||||
|
>
|
||||||
{signedInAsLabel}
|
{signedInAsLabel}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -156,5 +156,9 @@ export function PersonalAccountSettingsContainer(
|
|||||||
|
|
||||||
function useSupportMultiLanguage() {
|
function useSupportMultiLanguage() {
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
return i18n.options.supportedLngs && i18n.options.supportedLngs.length > 1;
|
const langs = (i18n.options.supportedLngs as string[]) ?? [];
|
||||||
|
|
||||||
|
const supportedLangs = langs.filter((lang) => lang !== 'cimode');
|
||||||
|
|
||||||
|
return supportedLangs.length > 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import type { User } from '@supabase/supabase-js';
|
import type { User } from '@supabase/supabase-js';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { CheckIcon } from '@radix-ui/react-icons';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@@ -74,11 +75,13 @@ export function UpdateEmailForm({
|
|||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
className={'flex flex-col space-y-4'}
|
className={'flex flex-col space-y-4'}
|
||||||
data-test={'update-email-form'}
|
data-test={'account-email-form'}
|
||||||
onSubmit={form.handleSubmit(updateEmail)}
|
onSubmit={form.handleSubmit(updateEmail)}
|
||||||
>
|
>
|
||||||
<If condition={updateUserMutation.data}>
|
<If condition={updateUserMutation.data}>
|
||||||
<Alert variant={'success'}>
|
<Alert variant={'success'}>
|
||||||
|
<CheckIcon className={'h-4'} />
|
||||||
|
|
||||||
<AlertTitle>
|
<AlertTitle>
|
||||||
<Trans i18nKey={'account:updateEmailSuccess'} />
|
<Trans i18nKey={'account:updateEmailSuccess'} />
|
||||||
</AlertTitle>
|
</AlertTitle>
|
||||||
@@ -99,7 +102,7 @@ export function UpdateEmailForm({
|
|||||||
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
data-test={'profile-new-email-input'}
|
data-test={'account-email-form-email-input'}
|
||||||
required
|
required
|
||||||
type={'email'}
|
type={'email'}
|
||||||
placeholder={''}
|
placeholder={''}
|
||||||
@@ -123,7 +126,7 @@ export function UpdateEmailForm({
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
data-test={'profile-repeat-email-input'}
|
data-test={'account-email-form-repeat-email-input'}
|
||||||
required
|
required
|
||||||
type={'email'}
|
type={'email'}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export function UpdateAccountDetailsForm({
|
|||||||
return toast.promise(() => promise, {
|
return toast.promise(() => promise, {
|
||||||
success: t(`updateProfileSuccess`),
|
success: t(`updateProfileSuccess`),
|
||||||
error: t(`updateProfileError`),
|
error: t(`updateProfileError`),
|
||||||
loading: t(`:pdateProfileLoading`),
|
loading: t(`updateProfileLoading`),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ export function UpdateAccountDetailsForm({
|
|||||||
<div className={'flex flex-col space-y-8'}>
|
<div className={'flex flex-col space-y-8'}>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
data-test={'update-profile-form'}
|
data-test={'update-account-name-form'}
|
||||||
className={'flex flex-col space-y-4'}
|
className={'flex flex-col space-y-4'}
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
>
|
>
|
||||||
@@ -72,7 +72,7 @@ export function UpdateAccountDetailsForm({
|
|||||||
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
data-test={'profile-display-name'}
|
data-test={'account-display-name'}
|
||||||
minLength={2}
|
minLength={2}
|
||||||
placeholder={''}
|
placeholder={''}
|
||||||
maxLength={100}
|
maxLength={100}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useCallback, useRef, useState } from 'react';
|
import { useCallback, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { Check } from 'lucide-react';
|
import { CheckCircledIcon } from '@radix-ui/react-icons';
|
||||||
|
|
||||||
import { useSignUpWithEmailAndPassword } from '@kit/supabase/hooks/use-sign-up-with-email-password';
|
import { useSignUpWithEmailAndPassword } from '@kit/supabase/hooks/use-sign-up-with-email-password';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
|
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
|
||||||
@@ -71,7 +71,7 @@ export function EmailPasswordSignUpContainer({
|
|||||||
function SuccessAlert() {
|
function SuccessAlert() {
|
||||||
return (
|
return (
|
||||||
<Alert variant={'success'}>
|
<Alert variant={'success'}>
|
||||||
<Check className={'w-4'} />
|
<CheckCircledIcon className={'w-4'} />
|
||||||
|
|
||||||
<AlertTitle>
|
<AlertTitle>
|
||||||
<Trans i18nKey={'auth:emailConfirmationAlertHeading'} />
|
<Trans i18nKey={'auth:emailConfirmationAlertHeading'} />
|
||||||
|
|||||||
@@ -87,6 +87,13 @@ function getCallbackUrl(props: {
|
|||||||
url.searchParams.set('invite_token', props.inviteToken);
|
url.searchParams.set('invite_token', props.inviteToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const searchParams = new URLSearchParams(window.location.search);
|
||||||
|
const next = searchParams.get('next');
|
||||||
|
|
||||||
|
if (next) {
|
||||||
|
url.searchParams.set('next', next);
|
||||||
|
}
|
||||||
|
|
||||||
return url.href;
|
return url.href;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user