Fix failing e2e tests (#460)

* fix broken tests

* navbar fixes + return pricing

* changes

* re-remove pricing
This commit is contained in:
Franjo Mindek
2025-07-30 15:19:59 +02:00
committed by GitHub
parent 0a7a04e63d
commit 3b7a67d5ae
11 changed files with 64 additions and 83 deletions

View File

@@ -97,7 +97,7 @@
+ </Button>
</WaspRouterLink>
) : (
<div className='space-y-2'>
<ul className='space-y-2'>
@@ -174,7 +187,14 @@
)}
</div>
@@ -114,7 +114,7 @@
</div>
</div>
</div>
@@ -217,6 +237,6 @@
@@ -218,6 +238,6 @@
'size-7': isScrolled,
})}
src={logo}

View File

@@ -1,6 +1,6 @@
--- template/app/src/client/components/NavBar/constants.ts
+++ opensaas-sh/app/src/client/components/NavBar/constants.ts
@@ -9,12 +9,12 @@
@@ -9,7 +9,6 @@
export const marketingNavigationItems: NavigationItem[] = [
{ name: 'Features', to: '/#features' },
@@ -8,9 +8,3 @@
...staticNavigationItems,
] as const;
export const demoNavigationitems: NavigationItem[] = [
{ name: 'AI Scheduler', to: routes.DemoAppRoute.to },
{ name: 'File Upload', to: routes.FileUploadRoute.to },
+ { name: 'Pricing', to: routes.PricingPageRoute.to },
...staticNavigationItems,
] as const;

View File

@@ -168,9 +168,9 @@ function NavBarMobileMenu({
</div>
</WaspRouterLink>
) : (
<div className='space-y-2'>
<ul className='space-y-2'>
<UserMenuItems user={user} onItemClick={() => setMobileMenuOpen(false)} />
</div>
</ul>
)}
</div>
<div className='py-6'>
@@ -202,6 +202,7 @@ function renderNavigationItems(
to={item.to}
className={menuStyles}
onClick={setMobileMenuOpen && (() => setMobileMenuOpen(false))}
target={item.to.startsWith('http') ? '_blank' : undefined}
>
{item.name}
</ReactRouterLink>

View File

@@ -16,5 +16,6 @@ export const marketingNavigationItems: NavigationItem[] = [
export const demoNavigationitems: NavigationItem[] = [
{ name: 'AI Scheduler', to: routes.DemoAppRoute.to },
{ name: 'File Upload', to: routes.FileUploadRoute.to },
{ name: 'Pricing', to: routes.PricingPageRoute.to },
...staticNavigationItems,
] as const;

View File

@@ -211,6 +211,7 @@ function NewTaskForm({ handleCreateTask }: { handleCreateTask: typeof createTask
variant='default'
size='default'
className='w-full'
data-testid='generate-schedule-button'
>
{isPlanGenerating ? (
<>
@@ -310,7 +311,7 @@ function Todo({ id, isDone, description, time }: TodoProps) {
function Schedule({ schedule }: { schedule: GeneratedSchedule }) {
return (
<div className='flex flex-col gap-6 py-6'>
<div className='flex flex-col gap-6 py-6' data-testid='schedule'>
<div className='space-y-4'>
{!!schedule.tasks ? (
schedule.tasks

View File

@@ -31,13 +31,13 @@ export function UserDropdown({ user }: { user: Partial<UserEntity> }) {
if (item.isAdminOnly && (!user || !user.isAdmin)) return null;
return (
<DropdownMenuItem asChild>
<DropdownMenuItem key={item.name}>
<WaspRouterLink
to={item.to}
onClick={() => {
setOpen(false);
}}
className='flex items-center gap-3.5 text-sm font-medium duration-300 ease-in-out hover:text-primary'
className='flex items-center gap-3 w-full'
>
<item.icon size='1.1rem' />
{item.name}
@@ -45,12 +45,11 @@ export function UserDropdown({ user }: { user: Partial<UserEntity> }) {
</DropdownMenuItem>
);
})}
<DropdownMenuItem
onClick={() => logout()}
className='flex items-center gap-3.5 text-sm font-medium duration-300 ease-in-out hover:text-primary'
>
<LogOut size='1.1rem' />
Log Out
<DropdownMenuItem>
<button type='button' onClick={() => logout()} className='flex items-center gap-3 w-full'>
<LogOut size='1.1rem' />
Log Out
</button>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

View File

@@ -12,30 +12,30 @@ export const UserMenuItems = ({ user, onItemClick }: { user?: Partial<User>; onI
if (item.isAdminOnly && (!user || !user.isAdmin)) return null;
return (
<div key={item.name} className='py-2'>
<li key={item.name}>
<WaspRouterLink
to={item.to}
onClick={onItemClick}
className='flex items-center gap-3.5 text-sm font-medium duration-300 ease-in-out text-foreground hover:text-primary'
className='flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium leading-7 text-foreground hover:bg-accent hover:text-accent-foreground transition-colors'
>
<item.icon size='1.1rem' />
{item.name}
</WaspRouterLink>
</div>
</li>
);
})}
<div className='py-2'>
<li>
<button
onClick={() => {
logout();
onItemClick?.();
}}
className='flex items-center gap-3.5 text-sm font-medium duration-300 ease-in-out text-foreground hover:text-primary'
className='flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium leading-7 text-foreground hover:bg-accent hover:text-accent-foreground transition-colors'
>
<LogOut size='1.1rem' />
Log Out
</button>
</div>
</li>
</>
);
};

View File

@@ -1,5 +1,5 @@
import { test, expect, type Page } from '@playwright/test';
import { signUserUp, logUserIn, createRandomUser, makeStripePayment, type User} from './utils';
import { expect, test, type Page } from '@playwright/test';
import { createRandomUser, logUserIn, makeStripePayment, signUserUp, type User } from './utils';
let page: Page;
let testUser: User;
@@ -23,8 +23,8 @@ test.afterAll(async () => {
await page.close();
});
const task1 = 'create presentation on SaaS';
const task2 = 'build SaaS app draft';
const task1 = 'Create presentation on SaaS';
const task2 = 'Build SaaS app draft';
test('User can make 3 AI schedule generations', async () => {
test.slow(); // Use a longer timeout time in case OpenAI is slow to respond
@@ -38,11 +38,13 @@ test('User can make 3 AI schedule generations', async () => {
await expect(page.getByText(task2)).toBeVisible();
for (let i = 0; i < 3; i++) {
const generateScheduleButton = page.getByRole('button', { name: 'Generate Schedule' });
const generateScheduleButton = page.getByTestId('generate-schedule-button');
await expect(generateScheduleButton).toBeVisible();
await Promise.all([
page.waitForRequest((req) => req.url().includes('operations/generate-gpt-response') && req.method() === 'POST'),
page.waitForRequest(
(req) => req.url().includes('operations/generate-gpt-response') && req.method() === 'POST'
),
page.waitForResponse((response) => {
return response.url().includes('/operations/generate-gpt-response') && response.status() === 200;
}),
@@ -50,13 +52,9 @@ test('User can make 3 AI schedule generations', async () => {
generateScheduleButton.click(),
]);
// We already show a table with some dummy data even before the API call
// Now we want to check that the tasks we added are in the generated table
const table = page.getByRole('table');
await expect(table).toBeVisible();
const tableTextContent = (await table.innerText()).toLowerCase();
expect(tableTextContent.includes(task1.toLowerCase())).toBeTruthy();
expect(tableTextContent.includes(task2.toLowerCase())).toBeTruthy();
const schedule = page.getByTestId('schedule');
expect(schedule).toContainText(task1, { ignoreCase: true });
expect(schedule).toContainText(task2, { ignoreCase: true });
}
});
@@ -65,12 +63,13 @@ test('AI schedule generation fails on 4th attempt', async () => {
await page.reload();
const generateScheduleButton = page.getByRole('button', { name: 'Generate Schedule' });
const generateScheduleButton = page.getByTestId('generate-schedule-button');
await expect(generateScheduleButton).toBeVisible();
await Promise.all([
page.waitForRequest((req) => req.url().includes('operations/generate-gpt-response') && req.method() === 'POST'),
page.waitForRequest(
(req) => req.url().includes('operations/generate-gpt-response') && req.method() === 'POST'
),
page.waitForResponse((response) => {
// expect the response to be 402 "PAYMENT_REQUIRED"
return response.url().includes('/operations/generate-gpt-response') && response.status() === 402;
@@ -79,14 +78,9 @@ test('AI schedule generation fails on 4th attempt', async () => {
generateScheduleButton.click(),
]);
// We already show a table with some dummy data even before the API call
// Now we want to check that the tasks were NOT added because the API call should have failed
const table = page.getByRole('table');
await expect(table).toBeVisible();
const tableTextContent = (await table.innerText()).toLowerCase();
expect(tableTextContent.includes(task1.toLowerCase())).toBeFalsy();
expect(tableTextContent.includes(task2.toLowerCase())).toBeFalsy();
const schedule = page.getByTestId('schedule');
expect(schedule).not.toContainText(task1, { ignoreCase: true });
expect(schedule).not.toContainText(task2, { ignoreCase: true });
});
test('Make test payment with Stripe for hobby plan', async () => {
@@ -97,12 +91,14 @@ test('Make test payment with Stripe for hobby plan', async () => {
test('User should be able to generate another schedule after payment', async () => {
await page.goto('/demo-app');
const generateScheduleButton = page.getByRole('button', { name: 'Generate Schedule' });
const generateScheduleButton = page.getByTestId('generate-schedule-button');
await expect(generateScheduleButton).toBeVisible();
await Promise.all([
page
.waitForRequest((req) => req.url().includes('operations/generate-gpt-response') && req.method() === 'POST')
.waitForRequest(
(req) => req.url().includes('operations/generate-gpt-response') && req.method() === 'POST'
)
.catch((err) => console.error(err.message)),
page
.waitForResponse((response) => {
@@ -116,10 +112,7 @@ test('User should be able to generate another schedule after payment', async ()
generateScheduleButton.click(),
]);
await page.waitForSelector('table');
const table = page.getByRole('table');
await expect(table).toBeVisible();
const tableTextContent = (await table.innerText()).toLowerCase();
expect(tableTextContent.includes(task1.toLowerCase())).toBeTruthy();
expect(tableTextContent.includes(task2.toLowerCase())).toBeTruthy();
const schedule = page.getByTestId('schedule');
expect(schedule).toContainText(task1, { ignoreCase: true });
expect(schedule).toContainText(task2, { ignoreCase: true });
});

View File

@@ -1,6 +1,4 @@
import { test, expect, Cookie } from '@playwright/test';
const DOCS_URL = 'https://docs.opensaas.sh';
import { Cookie, expect, test } from '@playwright/test';
test.describe('general landing page tests', () => {
test.beforeEach(async ({ page }) => {
@@ -13,16 +11,12 @@ test.describe('general landing page tests', () => {
test('get started link', async ({ page }) => {
await page.getByRole('link', { name: 'Get started' }).click();
await page.waitForURL(DOCS_URL);
await page.waitForURL('**/signup');
});
test('headings', async ({ page }) => {
await expect(
page.getByRole('heading', { name: 'Frequently asked questions' })
).toBeVisible();
await expect(
page.getByRole('heading', { name: 'Some cool words' })
).toBeVisible();
await expect(page.getByRole('heading', { name: 'Frequently asked questions' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Some cool words' })).toBeVisible();
});
});
@@ -31,10 +25,7 @@ test.describe('cookie consent tests', () => {
await page.goto('/');
});
test('cookie consent banner rejection does not set cc_cookie', async ({
context,
page,
}) => {
test('cookie consent banner rejection does not set cc_cookie', async ({ context, page }) => {
await page.$$('button:has-text("Reject all")');
await page.click('button:has-text("Reject all")');
@@ -44,10 +35,7 @@ test.describe('cookie consent tests', () => {
expect(cookieObject.categories.includes('analytics')).toBeFalsy();
});
test('cookie consent banner acceptance sets cc_cookie and _ga cookies', async ({
context,
page,
}) => {
test('cookie consent banner acceptance sets cc_cookie and _ga cookies', async ({ context, page }) => {
await page.$$('button:has-text("Accept all")');
await page.click('button:has-text("Accept all")');

View File

@@ -1,5 +1,12 @@
import { test, expect, type Page } from '@playwright/test';
import { signUserUp, logUserIn, createRandomUser, makeStripePayment, type User, acceptAllCookies } from './utils';
import { expect, test, type Page } from '@playwright/test';
import {
acceptAllCookies,
createRandomUser,
logUserIn,
makeStripePayment,
signUserUp,
type User,
} from './utils';
let page: Page;
let testUser: User;

View File

@@ -1,4 +1,4 @@
import { type Page, test, expect } from '@playwright/test';
import { expect, type Page } from '@playwright/test';
import { randomUUID } from 'crypto';
export type User = {
@@ -13,13 +13,11 @@ export const logUserIn = async ({ page, user }: { page: Page; user: User }) => {
await page.goto('/');
await page.getByRole('link', { name: 'Log in' }).click();
await page.waitForURL('**/login', {
waitUntil: 'domcontentloaded',
});
await page.fill('input[name="email"]', user.email);
await page.fill('input[name="password"]', DEFAULT_PASSWORD);
const clickLogin = page.click('button:has-text("Log in")');
@@ -39,7 +37,7 @@ export const logUserIn = async ({ page, user }: { page: Page; user: User }) => {
export const signUserUp = async ({ page, user }: { page: Page; user: User }) => {
await page.goto('/');
await page.evaluate(() => {
try {
const sessionId = localStorage.getItem('wasp:sessionId');
@@ -59,7 +57,6 @@ export const signUserUp = async ({ page, user }: { page: Page; user: User }) =>
await page.click('text="go to signup"');
await page.fill('input[name="email"]', user.email);
await page.fill('input[name="password"]', DEFAULT_PASSWORD);
await page.click('button:has-text("Sign up")');