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
40 changes: 40 additions & 0 deletions .github/workflows/phpunit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,43 @@ jobs:
- uses: ramsey/composer-install@a8d0d959dab41457692a5e2041bd9b757a119e3f # 3.2.1

- run: composer test

coverage:
name: Coverage
runs-on: ubuntu-latest
permissions:
contents: read
actions: write
services:
mariadb:
image: mariadb:11
env:
MARIADB_ROOT_PASSWORD: root
MARIADB_DATABASE: wordpress_test
ports:
- 3306:3306
options: >-
--health-cmd="healthcheck.sh --connect --innodb_initialized"
--health-interval=5s
--health-timeout=5s
--health-retries=10
env:
WP_TESTS_DB_HOST: 127.0.0.1
WP_TESTS_DB_NAME: wordpress_test
WP_TESTS_DB_USER: root
WP_TESTS_DB_PASSWORD: root
steps:
- uses: actions/checkout@v5

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

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

- run: composer coverage

- uses: k1LoW/octocov-action@a167dc0dee441b7ffc45e1b862ab55ec0d87f278 # v1.5.2
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 @@ -2,5 +2,8 @@
languages/*.mo
/vendor/
/vendor-bin/*/vendor/
/tests/.coverage/
/.phpunit.result.cache
/node_modules/
/tests/E2E/.results/
/tests/E2E/.report/
13 changes: 13 additions & 0 deletions .octocov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
coverage:
paths:
- tests/.coverage/clover.xml
acceptable: current >= 90% && current >= prev

diff:
datastores:
- artifact://${GITHUB_REPOSITORY}

report:
if: is_default_branch
datastores:
- artifact://${GITHUB_REPOSITORY}
4 changes: 4 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@
],
"test:unit": "phpunit",
"test:integration": "phpunit -c phpunit-integration.xml.dist",
"coverage": [
"@putenv XDEBUG_MODE=coverage",
"phpunit -c phpunit-coverage.xml.dist --coverage-clover=tests/.coverage/clover.xml"
],
"ci": [
"@lint",
"@cs",
Expand Down
48 changes: 48 additions & 0 deletions package-lock.json

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

9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@
"private": true,
"license": "GPL-2.0-or-later",
"scripts": {
"build:vendor": "rm -rf assets/vendor && mkdir -p assets/vendor/bootstrap assets/vendor/lineicons && cp node_modules/bootstrap/dist/css/bootstrap-grid.min.css assets/vendor/bootstrap/ && cp -r node_modules/lineicons/web-font/lineicons.css node_modules/lineicons/web-font/fonts assets/vendor/lineicons/"
"build:vendor": "rm -rf assets/vendor && mkdir -p assets/vendor/bootstrap assets/vendor/lineicons && cp node_modules/bootstrap/dist/css/bootstrap-grid.min.css assets/vendor/bootstrap/ && cp -r node_modules/lineicons/web-font/lineicons.css node_modules/lineicons/web-font/fonts assets/vendor/lineicons/",
"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"
},
"dependencies": {
"bootstrap": "5.3.0",
"lineicons": "1.0.3"
},
"devDependencies": {
"@playwright/test": "^1.63.0"
}
}
27 changes: 27 additions & 0 deletions phpunit-coverage.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor-bin/phpunit/vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="tests/bootstrap-integration.php"
colors="true"
failOnWarning="true"
failOnRisky="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTestsThatDoNotTestAnything="true"
convertDeprecationsToExceptions="false">
<testsuites>
<testsuite name="unit">
<directory suffix="Test.php">tests/Unit</directory>
</testsuite>
<testsuite name="integration">
<directory suffix="Test.php">tests/Integration</directory>
</testsuite>
</testsuites>
<coverage>
<include>
<file>functions.php</file>
<directory suffix=".php">inc</directory>
<directory suffix=".php">src</directory>
<directory suffix=".php">woocommerce</directory>
</include>
</coverage>
</phpunit>
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',
},
} );
19 changes: 19 additions & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ Every check is a Composer script:
- `composer stan`: PHPStan
- `composer test`: PHPUnit
- `composer ci`: all of the above, in this order
- `composer coverage`: PHPUnit with a coverage report for octocov

Every PHP file in `inc/`, `src/` and `woocommerce/` needs a test named after it,
at the same path: under `tests/Integration` for `inc/` and `woocommerce/`, which
need WordPress, and under `tests/Unit` or `tests/Integration` for `src/`. A browser
test under `tests/E2E` follows the same names. `tests/Unit/StructureTest.php`
enforces this and also fails on a test whose file no longer exists. In CI, octocov fails the run when line
coverage is below 90% or below the last report of `main` (`.octocov.yml`).

`composer install` brings in WordPress, the WordPress test suite and WooCommerce,
so the tests only need a MySQL/MariaDB database they are allowed to wipe on every
Expand All @@ -73,6 +81,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();
} );
} );
Loading
Loading