diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 297e7fd3d..8904b795d 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -32,11 +32,16 @@ jobs: - name: Install dependencies run: pnpm install + - name: Typecheck run: pnpm run typecheck + - name: Lint run: pnpm run lint + - name: Production Build (test env) + run: pnpm --filter web build:test + test: name: ⚫️ Test timeout-minutes: 8 diff --git a/apps/e2e/tests/team-accounts/team-accounts.po.ts b/apps/e2e/tests/team-accounts/team-accounts.po.ts index 4e51d33bc..eeefe25de 100644 --- a/apps/e2e/tests/team-accounts/team-accounts.po.ts +++ b/apps/e2e/tests/team-accounts/team-accounts.po.ts @@ -37,6 +37,12 @@ export class TeamAccountsPageObject { }).click(); } + async goToBilling() { + await this.page.locator('a', { + hasText: 'Billing', + }).click(); + } + async openAccountsSelector() { await this.page.click('[data-test="account-selector-trigger"]'); } diff --git a/apps/e2e/tests/team-billing/team-billing.po.ts b/apps/e2e/tests/team-billing/team-billing.po.ts new file mode 100644 index 000000000..f27cc3aae --- /dev/null +++ b/apps/e2e/tests/team-billing/team-billing.po.ts @@ -0,0 +1,18 @@ +import { Page } from '@playwright/test'; +import { StripePageObject } from '../utils/stripe.po'; +import { TeamAccountsPageObject } from '../team-accounts/team-accounts.po'; + +export class TeamBillingPageObject { + private readonly teamAccounts: TeamAccountsPageObject; + public readonly stripe: StripePageObject; + + constructor(page: Page) { + this.teamAccounts = new TeamAccountsPageObject(page); + this.stripe = new StripePageObject(page); + } + + async setup() { + await this.teamAccounts.setup(); + await this.teamAccounts.goToBilling(); + } +} \ No newline at end of file diff --git a/apps/e2e/tests/team-billing/team-billing.spec.ts b/apps/e2e/tests/team-billing/team-billing.spec.ts new file mode 100644 index 000000000..dd46465be --- /dev/null +++ b/apps/e2e/tests/team-billing/team-billing.spec.ts @@ -0,0 +1,24 @@ +import { expect, Page, test } from '@playwright/test'; +import { TeamBillingPageObject } from './team-billing.po'; + +test.describe('Team Billing', () => { + let page: Page; + let billing: TeamBillingPageObject; + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); + billing = new TeamBillingPageObject(page); + + await billing.setup(); + }); + + test('a team can subscribe to a plan', async () => { + await billing.stripe.selectPlan(0); + await billing.stripe.proceedToCheckout(); + + await billing.stripe.fillForm(); + await billing.stripe.submitForm(); + + await expect(billing.stripe.successStatus()).toBeVisible(); + }); +}); \ No newline at end of file diff --git a/apps/e2e/tests/user-billing/user-billing.po.ts b/apps/e2e/tests/user-billing/user-billing.po.ts new file mode 100644 index 000000000..0468cb2c2 --- /dev/null +++ b/apps/e2e/tests/user-billing/user-billing.po.ts @@ -0,0 +1,17 @@ +import { Page } from '@playwright/test'; +import { AuthPageObject } from '../authentication/auth.po'; +import { StripePageObject } from '../utils/stripe.po'; + +export class UserBillingPageObject { + private readonly auth: AuthPageObject; + public readonly stripe: StripePageObject; + + constructor(page: Page) { + this.auth = new AuthPageObject(page); + this.stripe = new StripePageObject(page); + } + + async setup() { + await this.auth.signUpFlow('/home/billing'); + } +} \ No newline at end of file diff --git a/apps/e2e/tests/user-billing/user-billing.spec.ts b/apps/e2e/tests/user-billing/user-billing.spec.ts new file mode 100644 index 000000000..9ef729356 --- /dev/null +++ b/apps/e2e/tests/user-billing/user-billing.spec.ts @@ -0,0 +1,24 @@ +import { expect, Page, test } from '@playwright/test'; +import { UserBillingPageObject } from './user-billing.po'; + +test.describe('User Billing', () => { + let page: Page; + let billing: UserBillingPageObject; + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); + billing = new UserBillingPageObject(page); + + await billing.setup(); + }); + + test('user can subscribe to a plan', async () => { + await billing.stripe.selectPlan(0); + await billing.stripe.proceedToCheckout(); + + await billing.stripe.fillForm(); + await billing.stripe.submitForm(); + + await expect(billing.stripe.successStatus()).toBeVisible(); + }); +}); \ No newline at end of file diff --git a/apps/e2e/tests/utils/stripe.po.ts b/apps/e2e/tests/utils/stripe.po.ts new file mode 100644 index 000000000..ecbee69ee --- /dev/null +++ b/apps/e2e/tests/utils/stripe.po.ts @@ -0,0 +1,87 @@ +import { Page, expect } from '@playwright/test'; + +export class StripePageObject { + private page: Page; + + constructor(page: Page) { + this.page = page; + } + + plans() { + return this.page.locator('[data-test-plan]'); + } + + getStripeCheckoutIframe() { + return this.page.frameLocator('[name="embedded-checkout"]'); + } + + async fillForm(params: { + billingName?: string; + cardNumber?: string; + expiry?: string; + cvc?: string; + billingCountry?: string; + } = {}) { + expect(() => { + return this.getStripeCheckoutIframe().locator('form').isVisible(); + }); + + const billingName = this.billingName(); + const cardNumber = this.cardNumber(); + const expiry = this.expiry(); + const cvc = this.cvc(); + const billingCountry = this.billingCountry(); + + await billingName.fill(params.billingName ?? 'Mr Makerkit'); + await cardNumber.fill(params.cardNumber ?? '4242424242424242'); + await expiry.fill(params.expiry ?? '1228'); + await cvc.fill(params.cvc ?? '123'); + await billingCountry.selectOption(params.billingCountry ?? 'IT'); + } + + submitForm() { + return this.getStripeCheckoutIframe().locator('form button').click(); + } + + cardNumber() { + return this.getStripeCheckoutIframe().locator('#cardNumber'); + } + + cvc() { + return this.getStripeCheckoutIframe().locator('#cardCvc'); + } + + expiry() { + return this.getStripeCheckoutIframe().locator('#cardExpiry'); + } + + billingName() { + return this.getStripeCheckoutIframe().locator('#billingName'); + } + + cardForm() { + return this.getStripeCheckoutIframe().locator('form'); + } + + billingCountry() { + return this.getStripeCheckoutIframe().locator('#billingCountry'); + } + + selectPlan(index: number = 0) { + const plans = this.plans(); + + return plans.nth(index).click(); + } + + manageBillingButton() { + return this.page.locator('manage-billing-redirect-button'); + } + + successStatus() { + return this.page.locator('[data-test="payment-return-success"]'); + } + + proceedToCheckout() { + return this.page.click('[data-test="checkout-submit-button"]'); + } +} \ No newline at end of file diff --git a/apps/web/app/(marketing)/_components/site-page-header.tsx b/apps/web/app/(marketing)/_components/site-page-header.tsx index 5fded2637..b767f9434 100644 --- a/apps/web/app/(marketing)/_components/site-page-header.tsx +++ b/apps/web/app/(marketing)/_components/site-page-header.tsx @@ -10,12 +10,8 @@ export function SitePageHeader(props: {