Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Playwright

on:
push:
branches:
- main
pull_request:

permissions:
contents: read

jobs:
playwright:
name: Playwright
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.3'
coverage: none
tools: composer

- uses: ramsey/composer-install@a8d0d959dab41457692a5e2041bd9b757a119e3f # 3.2.1

- uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm

- run: npm ci

- run: npx playwright install --with-deps chromium

- run: npm run env:start

- run: npm run test:e2e

- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: tests/E2E/.report
include-hidden-files: true
retention-days: 7
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ languages/*.mo
/vendor/
/vendor-bin/*/vendor/
/.phpunit.result.cache
/node_modules/
/tests/E2E/.results/
/tests/E2E/.report/
59 changes: 59 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "libresign-wordpress-theme",
"private": true,
"license": "GPL-2.0-or-later",
"description": "Browser tests for the LibreSign WordPress theme",
"scripts": {
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"env:start": "docker compose -f tests/E2E/docker-compose.yml up -d --wait && docker compose -f tests/E2E/docker-compose.yml run --rm cli",
"env:stop": "docker compose -f tests/E2E/docker-compose.yml down -v"
},
"devDependencies": {
"@playwright/test": "^1.63.0"
}
}
25 changes: 25 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { defineConfig, devices } from '@playwright/test';

export default defineConfig( {
testDir: 'tests/E2E',
outputDir: 'tests/E2E/.results',
workers: 1,
fullyParallel: false,
forbidOnly: Boolean( process.env.CI ),
retries: process.env.CI ? 1 : 0,
reporter: process.env.CI
? [
[ 'github' ],
[
'html',
{ open: 'never', outputFolder: 'tests/E2E/.report' },
],
]
: [ [ 'list' ] ],
use: {
...devices[ 'Desktop Chrome' ],
baseURL: 'http://localhost:8889',
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
},
} );
11 changes: 11 additions & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@ On the local SaaS stack:
docker exec -w /var/www/html/wp-content/themes/libresign \
wordpress-docker-wordpress-1 composer test

The browser tests in `tests/E2E` cover creating a workspace together with a plan,
the CPF/CNPJ field and the policy consent at checkout, and the lost password page.
They need Docker, Node.js and a `composer install`, since the stack mounts
WooCommerce from `vendor/test-plugins`:

npm ci
npx playwright install chromium
npm run env:start # WordPress on :8889
npm run test:e2e
npm run env:stop

== Development notes ==

- Site fragment sync/render bootstrap: `inc/footer-fragment.php`
Expand Down
18 changes: 18 additions & 0 deletions tests/E2E/Inc/Account.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { expect, test } from '@playwright/test';

test.describe( 'Recovering the password', () => {
test( 'opens the lost password form on its own page', async ( { page } ) => {
await page.goto( '/lost-password/' );

await expect( page.getByRole( 'heading', { name: 'Lost password', level: 1 } ) ).toBeVisible();
await expect( page.getByLabel( /Email or username/ ) ).toBeVisible();
await expect( page.getByRole( 'link', { name: 'Back to sign in' } ) ).toHaveAttribute( 'href', /\/my-account\/$/ );
} );

test( 'offers the lost password form from the account page', async ( { page } ) => {
await page.goto( '/my-account/' );
await page.getByRole( 'link', { name: 'Lost your password?' } ).click();

await expect( page.getByRole( 'button', { name: 'Send reset link' } ) ).toBeVisible();
} );
} );
30 changes: 30 additions & 0 deletions tests/E2E/Inc/Checkout.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { expect, test } from '@playwright/test';

import { addThePlanToTheCart, fillTheBillingAddress, placeOrder } from '../support/store';

const CONSENT = 'I agree to the terms and privacy policy before placing the order.';

test.describe( 'Agreeing to the policy at checkout', () => {
test.beforeEach( async ( { page } ) => {
await addThePlanToTheCart( page );
await fillTheBillingAddress( page, 'Brazil', '529.982.247-25' );
} );

test( 'links the terms and privacy policy', async ( { page } ) => {
await expect( page.getByRole( 'link', { name: 'terms and privacy policy' } ) ).toHaveAttribute( 'href', 'https://libresign.coop/privacy-policy' );
} );

test( 'refuses the order without the consent', async ( { page } ) => {
await placeOrder( page );

await expect( page.getByText( 'You must agree to the policies before completing the purchase.' ) ).toBeVisible();
} );

test( 'places the order with the consent', async ( { page } ) => {
await page.getByLabel( CONSENT ).check();
await placeOrder( page );

await expect( page ).toHaveURL( /\/order-received\// );
await expect( page.getByText( 'Thank you. Your order has been received.' ) ).toBeVisible();
} );
} );
32 changes: 32 additions & 0 deletions tests/E2E/Inc/CpfCnpj.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { expect, test } from '@playwright/test';

import { addThePlanToTheCart, fillTheBillingAddress, placeOrder } from '../support/store';

test.describe( 'Asking Brazilian customers for their CPF or CNPJ', () => {
test.beforeEach( async ( { page } ) => {
await addThePlanToTheCart( page );
} );

test( 'shows the field only in Brazil', async ( { page } ) => {
await fillTheBillingAddress( page, 'Brazil' );
await expect( page.getByLabel( 'CPF or CNPJ' ) ).toBeVisible();

await page.locator( '#billing-country' ).selectOption( { label: 'Portugal' } );

await expect( page.getByLabel( 'CPF or CNPJ' ) ).toBeHidden();
} );

test( 'requires the field in Brazil', async ( { page } ) => {
await fillTheBillingAddress( page, 'Brazil' );
await placeOrder( page );

await expect( page.getByText( 'Please enter your CPF or CNPJ.' ) ).toBeVisible();
} );

test( 'refuses an invalid CPF', async ( { page } ) => {
await fillTheBillingAddress( page, 'Brazil', '529.982.247-24' );
await placeOrder( page );

await expect( page.getByText( 'Please enter a valid CPF or CNPJ.' ) ).toBeVisible();
} );
} );
41 changes: 41 additions & 0 deletions tests/E2E/Inc/Registration.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { expect, Page, test } from '@playwright/test';

import { addThePlanToTheCart } from '../support/store';

async function fillTheWorkspaceForm( page: Page ): Promise< void > {
await page.goto( '/my-account/' );
await page.waitForFunction( () => 'zxcvbn' in window );
await page.locator( '#reg_email' ).fill( `workspace-${ Date.now() }@example.org` );
await page.locator( '#reg_password' ).pressSequentially( 'a strong password 123' );
}

test.describe( 'Creating a workspace together with a plan', () => {
test( 'sends a visitor to choose a plan first', async ( { page } ) => {
await page.goto( '/my-account/' );

await expect( page.getByRole( 'heading', { name: 'Create your workspace' } ) ).toBeHidden();
await page.getByRole( 'link', { name: 'Choose a plan' } ).click();

await expect( page ).toHaveURL( /\/shop\/$/ );
} );

test( 'creates the workspace and continues to checkout', async ( { page } ) => {
await addThePlanToTheCart( page );
await fillTheWorkspaceForm( page );

await page.locator( '#libresign_workspace_terms' ).check();
await page.getByRole( 'button', { name: 'Continue to checkout' } ).click();

await expect( page ).toHaveURL( /\/checkout\/$/ );
} );

test( 'refuses the workspace without the terms consent', async ( { page } ) => {
await addThePlanToTheCart( page );
await fillTheWorkspaceForm( page );

await page.locator( '#libresign_workspace_terms' ).evaluate( ( checkbox ) => checkbox.removeAttribute( 'required' ) );
await page.getByRole( 'button', { name: 'Continue to checkout' } ).click();

await expect( page.getByText( 'You must accept the terms to create your workspace.' ) ).toBeVisible();
} );
} );
55 changes: 55 additions & 0 deletions tests/E2E/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: libresign-theme-e2e

services:
mariadb:
image: mariadb:11
environment:
MARIADB_ROOT_PASSWORD: root
MARIADB_DATABASE: wordpress
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 5s
retries: 20

wordpress:
image: wordpress:7.1-php8.3-apache
depends_on:
mariadb:
condition: service_healthy
environment:
WORDPRESS_DB_HOST: mariadb
WORDPRESS_DB_USER: root
WORDPRESS_DB_PASSWORD: root
WORDPRESS_DB_NAME: wordpress
ports:
- "8889:80"
healthcheck:
test: ["CMD", "test", "-f", "/var/www/html/wp-config.php"]
interval: 1s
retries: 60
volumes:
- wordpress:/var/www/html
- ../..:/var/www/html/wp-content/themes/libresign:ro
- ../../vendor/test-plugins/woocommerce:/var/www/html/wp-content/plugins/woocommerce:ro

cli:
image: wordpress:cli-php8.3
profiles: ["cli"]
user: "33:33"
depends_on:
wordpress:
condition: service_healthy
environment:
WORDPRESS_DB_HOST: mariadb
WORDPRESS_DB_USER: root
WORDPRESS_DB_PASSWORD: root
WORDPRESS_DB_NAME: wordpress
volumes_from:
- wordpress
volumes:
- ./support/setup.sh:/setup.sh:ro
- ./support/seed.php:/seed.php:ro
command: ["sh", "/setup.sh"]

volumes:
wordpress:
10 changes: 10 additions & 0 deletions tests/E2E/support/seed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

WC_Install::create_pages();

$plan = new WC_Product_Simple();
$plan->set_name( 'Basic' );
$plan->set_slug( 'basic' );
$plan->set_regular_price( '55' );
$plan->set_virtual( true );
$plan->save();
18 changes: 18 additions & 0 deletions tests/E2E/support/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/sh
set -e

wp core install --url=http://localhost:8889 --title=LibreSign --admin_user=admin --admin_password=password --admin_email=admin@example.org --skip-email
wp rewrite structure '/%postname%/'
wp plugin activate woocommerce
wp theme activate libresign

wp option update woocommerce_coming_soon no
wp option update woocommerce_default_country BR:RJ
wp option update woocommerce_enable_myaccount_registration yes
wp option update woocommerce_registration_generate_username yes
wp option update woocommerce_registration_generate_password no
wp option update woocommerce_cheque_settings '{"enabled":"yes","title":"Check payments"}' --format=json

if [ -z "$(wp post list --post_type=product --name=basic --format=ids)" ]; then
wp eval-file /seed.php
fi
Loading
Loading