mirror of
https://github.com/wasp-lang/open-saas.git
synced 2025-09-28 04:28:40 +02:00
Fix failing e2e tests (#460)
* fix broken tests * navbar fixes + return pricing * changes * re-remove pricing
This commit is contained in:
@@ -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}
|
||||
|
@@ -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;
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -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 });
|
||||
});
|
||||
|
@@ -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")');
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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")');
|
||||
|
Reference in New Issue
Block a user