Skip to content

Commit

Permalink
improve tests
Browse files Browse the repository at this point in the history
  • Loading branch information
vincanger committed Mar 7, 2024
1 parent 0c277e5 commit bbbbcbd
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 51 deletions.
8 changes: 3 additions & 5 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
steps:
- name: Checkout the repo
uses: actions/checkout@v4

- name: Install Wasp
run: curl -sSL https://get.wasp-lang.dev/installer.sh | sh -s

Expand All @@ -39,10 +39,8 @@ jobs:
- name: Setup Env Vars not in github secrets
run: |
cd app
ls -la
test -f .env.server && echo ".env.server exists" || echo ".env.server does not exist"
cp .env.server.example .env.server
- name: Set up Playwright
run: |
cd app
Expand All @@ -53,4 +51,4 @@ jobs:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
cd app
DEBUG=pw:webserver npx playwright test
DEBUG=pw:webserver npx playwright test
5 changes: 2 additions & 3 deletions .github/workflows/retag-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ on:
push:
branches:
- main


jobs:
retag:
Expand All @@ -17,7 +16,7 @@ jobs:
run: |
git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"
- name: Delete Old Tag
run: |
git tag -d wasp-v0.12-template || true
Expand All @@ -26,4 +25,4 @@ jobs:
- name: Add New Tag
run: |
git tag wasp-v0.12-template
git push origin wasp-v0.12-template
git push origin wasp-v0.12-template
31 changes: 31 additions & 0 deletions app/ci-start-app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import cp from 'child_process';
import readline from 'linebyline';

function spawn(name, cmd, args, done) {
const spawnOptions = {
detached: true,
};
const proc = cp.spawn(cmd, args, spawnOptions);

// We close stdin stream on the new process because otherwise the start-app
// process hangs.
// See https://github.com/wasp-lang/wasp/pull/1218#issuecomment-1599098272.
proc.stdin.destroy();

readline(proc.stdout).on('line', (data) => {
console.log(`\x1b[0m\x1b[33m[${name}][out]\x1b[0m ${data}`);
});
readline(proc.stderr).on('line', (data) => {
console.log(`\x1b[0m\x1b[33m[${name}][err]\x1b[0m ${data}`);
});
proc.on('exit', done);
}

// Exit if either child fails
const cb = (code) => {
if (code !== 0) {
process.exit(code);
}
};
spawn('app', 'npm', ['run', 'example-app:start-app'], cb);
spawn('db', 'npm', ['run', 'example-app:start-db'], cb);
4 changes: 2 additions & 2 deletions app/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "opensaas",
"scripts": {
"example-app:start": "npm-run-all --parallel \"example-app:start-db\" \"example-app:start-app\"",
"example-app:start": "node ci-start-app.js",
"example-app:start-db": "npm run example-app:cleanup-db && wasp start db",
"example-app:start-app": "npm run example-app:wait-for-db && wasp db migrate-dev && wasp start",
"example-app:wait-for-db": "npx wait-port 5432",
Expand Down Expand Up @@ -44,4 +44,4 @@
"typescript": "^5.1.0",
"vite": "^4.3.9"
}
}
}
2 changes: 1 addition & 1 deletion app/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default defineConfig({
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://127.0.0.1:3000',
baseURL: 'http://127.0.0.1:3000',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
Expand Down
8 changes: 5 additions & 3 deletions app/playwright/tests/landingPageTests.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { test, expect } from '@playwright/test';
import { DOCS_URL } from '../../src/shared/constants';

test.describe('general landing page tests', () => {
test.beforeEach(async ({ page }) => {
await page.goto('localhost:3000');
await page.goto('/');
});

test('has title', async ({ page }) => {
Expand All @@ -12,10 +13,11 @@ 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);
});

test('headings', async ({ page }) => {
expect(page.getByRole('heading', { name: 'SaaS' })).toBeTruthy();
expect(page.getByRole('heading', { name: 'Features' })).toBeTruthy();
await expect(page.getByRole('heading', { name: 'Frequently asked questions' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Some cool words' })).toBeVisible();
});
});
23 changes: 17 additions & 6 deletions app/playwright/tests/paidUserTests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,24 @@ const test = createLoggedInUserFixture({ hasPaid: true, credits: 10 });

// test /demo-app page by entering "todo" and clicking add task
test('Demo App: add tasks & generate schedule', async ({ loggedInPage }) => {
expect(loggedInPage.url()).toBe('http://localhost:3000/demo-app');
const task1 = 'create presentation on SaaS';
const task2 = 'build SaaS app draft';

await loggedInPage.waitForURL('/demo-app');

// Fill input id="description" with "create presentation"
await loggedInPage.fill('input[id="description"]', 'create presentation on SaaS');
await loggedInPage.fill('input[id="description"]', task1);

// Click button:has-text("Add task")
await loggedInPage.click('button:has-text("Add task")');

await loggedInPage.fill('input[id="description"]', 'build SaaS app draft');
await loggedInPage.fill('input[id="description"]', task2);

await loggedInPage.click('button:has-text("Add task")');

// expect to find text in a span element
expect(loggedInPage.getByText('create presentation on SaaS')).toBeTruthy();
expect(loggedInPage.getByText('build SaaS app draft')).toBeTruthy();
expect(loggedInPage.getByText(task1)).toBeTruthy();
expect(loggedInPage.getByText(task2)).toBeTruthy();

// find a button with text "Generate Schedule" and check it's visible
const generateScheduleButton = loggedInPage.getByRole('button', { name: 'Generate Schedule' });
Expand All @@ -31,12 +34,20 @@ test('Demo App: add tasks & generate schedule', async ({ loggedInPage }) => {
(req) => req.url().includes('operations/generate-gpt-response') && req.method() === 'POST'
),
loggedInPage.waitForResponse((response) => {
if (response.url() === 'http://localhost:3001/operations/generate-gpt-response' && response.status() === 200) {
if (response.url().includes('/operations/generate-gpt-response') && response.status() === 200) {
return true;
}
return false;
}),
// We already started waiting before we perform the click that triggers the API calls. So now we just perform the click
generateScheduleButton.click(),
]);

const table = loggedInPage.getByRole('table');
await expect(table).toBeVisible();
const tableTextContent = (await table.innerText()).toLowerCase();
console.log(tableTextContent)

expect(tableTextContent.includes(task1.toLowerCase())).toBeTruthy();
expect(tableTextContent.includes(task2.toLowerCase())).toBeTruthy();
});
27 changes: 20 additions & 7 deletions app/playwright/tests/unpaidUserTests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,49 @@ import { createLoggedInUserFixture } from './utils';
const test = createLoggedInUserFixture({ hasPaid: false, credits: 0 });

test('Demo app: cannot generate schedule', async ({ loggedInPage }) => {
await loggedInPage.waitForURL('http://localhost:3000/demo-app');
const task1 = 'create presentation on SaaS';
const task2 = 'build SaaS app draft';

await loggedInPage.waitForURL('/demo-app');

// Fill input id="description" with "create presentation"
await loggedInPage.fill('input[id="description"]', 'create presentation on SaaS');
await loggedInPage.fill('input[id="description"]', task1);

// Click button:has-text("Add task")
await loggedInPage.click('button:has-text("Add task")');

await loggedInPage.fill('input[id="description"]', 'build SaaS app draft');
await loggedInPage.fill('input[id="description"]', task2);

await loggedInPage.click('button:has-text("Add task")');

// expect to find text in a span element
expect(loggedInPage.getByText('create presentation on SaaS')).toBeTruthy();
expect(loggedInPage.getByText('build SaaS app draft')).toBeTruthy();
expect(loggedInPage.getByText(task1)).toBeTruthy();
expect(loggedInPage.getByText(task2)).toBeTruthy();

// find a button with text "Generate Schedule" and check it's visible
const generateScheduleButton = loggedInPage.getByRole('button', { name: 'Generate Schedule' });
expect(generateScheduleButton).toBeTruthy();

await Promise.all([
loggedInPage.waitForRequest((req) => req.url().includes('operations/generate-gpt-response') && req.method() === 'POST'),
loggedInPage.waitForRequest(
(req) => req.url().includes('operations/generate-gpt-response') && req.method() === 'POST'
),
loggedInPage.waitForResponse((response) => {
// expect the response to be 402 "PAYMENT_REQUIRED"
if (response.url() === 'http://localhost:3001/operations/generate-gpt-response' && response.status() === 402) {
if (response.url().includes('/operations/generate-gpt-response') && response.status() === 402) {
return true;
}
return false;
}),
// We already started waiting before we perform the click that triggers the API calls. So now we just perform the click
generateScheduleButton.click(),
]);

// we already show a table with some dummy data even before the API call
const table = loggedInPage.getByRole('table');
await expect(table).toBeVisible();
const tableTextContent = (await table.innerText()).toLowerCase();

expect(tableTextContent.includes(task1.toLowerCase())).toBeFalsy();
expect(tableTextContent.includes(task2.toLowerCase())).toBeFalsy();
});
50 changes: 26 additions & 24 deletions app/playwright/tests/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { test as base, type Page } from '@playwright/test';
import { PrismaClient } from '@prisma/client';
// Create a new Prisma client to interact with DB
import { randomUUID } from 'crypto';

export const prisma = new PrismaClient();

export type User = {
Expand All @@ -17,7 +18,7 @@ export const logUserIn = async ({ page, user }: { page: Page; user: User }) => {
// Click the get started link.
await page.getByRole('link', { name: 'Log in' }).click();

console.log('logging in...', user)
console.log('logging in...', user);
await page.waitForURL('http://localhost:3000/login');
console.log('url', page.url());

Expand All @@ -32,7 +33,7 @@ export const logUserIn = async ({ page, user }: { page: Page; user: User }) => {
};

export const signUserUp = async ({ page, user }: { page: Page; user: User }) => {
await page.goto('localhost:3000');
await page.goto('/');

// Click the get started link.
await page.getByRole('link', { name: 'Log in' }).click();
Expand All @@ -51,28 +52,29 @@ export const signUserUp = async ({ page, user }: { page: Page; user: User }) =>
};

export const createRandomUser = () => {
const username = `user${Math.random().toString(36).substring(7)}`;
const password = `password${Math.random().toString(36).substring(7)}!`;
const username = `user${randomUUID()}`;
const password = `password${randomUUID()}!`;

return { username, password };
};

export const createLoggedInUserFixture = ({ hasPaid, credits }: Pick<User, 'hasPaid' | 'credits'>) => base.extend<{ loggedInPage: Page; testUser: User }>({
testUser: async ({}, use) => {
const { username, password } = createRandomUser();
await use({ username, password, hasPaid, credits });
},
loggedInPage: async ({ page, testUser }, use) => {
await signUserUp({ page, user: testUser });
await page.waitForURL('http://localhost:3000/demo-app');
const user = await prisma.user.update({
where: { username: testUser.username },
data: { hasPaid: testUser.hasPaid, credits: testUser.credits },
});
await use(page);
// clean up all that nasty data 🤮
await prisma.gptResponse.deleteMany({ where: { userId: user.id } });
await prisma.task.deleteMany({ where: { userId: user.id } });
await prisma.user.delete({ where: { id: user.id } });
},
});
export const createLoggedInUserFixture = ({ hasPaid, credits }: Pick<User, 'hasPaid' | 'credits'>) =>
base.extend<{ loggedInPage: Page; testUser: User }>({
testUser: async ({}, use) => {
const { username, password } = createRandomUser();
await use({ username, password, hasPaid, credits });
},
loggedInPage: async ({ page, testUser }, use) => {
await signUserUp({ page, user: testUser });
await page.waitForURL('/demo-app');
const user = await prisma.user.update({
where: { username: testUser.username },
data: { hasPaid: testUser.hasPaid, credits: testUser.credits },
});
await use(page);
// clean up all that nasty data 🤮
await prisma.gptResponse.deleteMany({ where: { userId: user.id } });
await prisma.task.deleteMany({ where: { userId: user.id } });
await prisma.user.delete({ where: { id: user.id } });
},
});

0 comments on commit bbbbcbd

Please sign in to comment.