Skip to content

Commit

Permalink
add e2e tests
Browse files Browse the repository at this point in the history
  • Loading branch information
lovasoa committed Aug 5, 2024
1 parent c76610a commit 6cc4cce
Show file tree
Hide file tree
Showing 10 changed files with 371 additions and 72 deletions.
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: End to end Tests
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
timeout-minutes: 10
defaults:
run:
working-directory: ./tests/end-to-end
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up cargo cache
uses: Swatinem/rust-cache@378c8285a4eaf12899d11bea686a763e906956af
- uses: actions/setup-node@v4
with:
node-version: lts/*
cache: 'npm'
cache-dependency-path: ./tests/end-to-end/package-lock.json
- name: Install dependencies
run: >
npm ci
npx playwright install --with-deps chromium
- name: build sqlpage
run: cargo build
working-directory: ./examples/official-site
- name: start official site and wait for it to be ready
timeout-minutes: 1
run: >
cargo run 2>/tmp/stderrlog &
tail -f /tmp/stderrlog | grep -q "started successfully"
working-directory: ./examples/official-site
- name: Run Playwright tests
run: npx playwright test
- name: show server logs
if: failure()
run: cat /tmp/stderrlog
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 0.26.0

- fix ugly wrapping of items in the header when the page title is long. We now have a nice text ellipsis (...) when the title is too long.
- re-add a link to the website title in the shell component
- add `text` and `post_html` properties to the [html](https://sql.ophir.dev/documentation.sql?component=html#component) component. This allows to include sanitized user-generated content in the middle of custom HTML.
- allow loading javascript ESM modules in the shell component
Expand All @@ -14,6 +15,7 @@
- fixed a bug where a form input with a value of `0` would diplay as empty instead of showing the `0`.
- reduce the margin at the botton of forms to make them more compact.
- fix [datagrid](https://sql.ophir.dev/documentation.sql?component=datagrid#component) color pills display when they contain long text.
- fix the "started successfully" message being displayed before the error message when the server failed to start.

## 0.25.0 (2024-07-13)

Expand Down
5 changes: 4 additions & 1 deletion examples/official-site/documentation.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ where $component is not null and not exists (select 1 from component where name
-- This line, at the top of the page, tells web browsers to keep the page locally in cache once they have it.
select 'http_header' as component, 'public, max-age=600, stale-while-revalidate=3600, stale-if-error=86400' as "Cache-Control";

select 'dynamic' as component, properties FROM example WHERE component = 'shell' LIMIT 1;
select 'dynamic' as component, json_patch(json_extract(properties, '$[0]'), json_object(
'title', coalesce($component || ' - ', '') || 'SQLPage Documentation'
)) as properties
FROM example WHERE component = 'shell' LIMIT 1;

select 'text' as component, format('SQLPage v%s documentation', sqlpage.version()) as title;
select '
Expand Down
2 changes: 1 addition & 1 deletion examples/official-site/examples/handle_picture_upload.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ select 'Your picture' as title,
'Uploaded file type: ' || sqlpage.uploaded_file_mime_type('my_file') as description
where $data_url is not null;

select 'form' as component;
select 'form' as component, 'Upload picture' as validate;
select 'my_file' as name, 'file' as type, 'Picture' as label;

select 'text' as component, '
Expand Down
140 changes: 70 additions & 70 deletions sqlpage/templates/shell.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
{{#if (or (or title (or icon image)) menu_item)}}
<header id="sqlpage_header">
<nav class="navbar navbar-expand-md navbar-light{{#if fixed_top_menu}} fixed-top{{/if}}">
<div class="container-fluid gap-2">
<div class="container-fluid gap-2 flex-nowrap justify-content-start" style="min-width:0">
<a class="navbar-brand" href="{{#if link}}{{link}}{{else}}/{{/if}}">
{{#if image}}
<img src="{{image}}" alt="{{title}}" width="32" height="32"
Expand All @@ -81,83 +81,83 @@
{{~icon_img icon~}}
{{/if}}
</a>
{{#if title}}<h1 class="mb-0 fs-2 text-truncate"><a class="navbar-brand" href="{{#if link}}{{link}}{{else}}/{{/if}}">{{title}}</a></h1>{{/if}}
{{#if title}}<h1 class="mb-0 fs-2 text-truncate flex-grow-1"><a href="{{#if link}}{{link}}{{else}}/{{/if}}">{{title}}</a></h1>{{/if}}
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#navbar-menu" aria-controls="navbar-menu" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbar-menu">
<ul class="navbar-nav ms-auto">
{{~#each (to_array menu_item)~}}
{{~#if (or (eq (typeof this) 'object') (and (eq (typeof this) 'string') (starts_with this '{')))}}
{{~#with (parse_json this)}}
{{#if (or (or this.title this.icon) this.image)}}
<li class="nav-item{{#if this.submenu}} dropdown{{/if}}">
<a class="nav-link {{#if this.submenu}}dropdown-toggle{{/if}}" href="{{#if this.link}}{{this.link}}{{else}}#{{/if}}"
{{~#if this.submenu}} data-bs-toggle="dropdown" data-bs-auto-close="outside" {{/if~}}
role="button"
>
{{~#if this.image~}}
<span {{~#if this.title}} class="me-1"{{/if}}>
{{~#if (eq this.size 'sm')}}
<img width=16 height=16 src="{{this.image}}">
{{~else~}}
<img width=24 height=24 src="{{this.image}}">
{{~/if~}}
</span>
{{~/if~}}
{{#if this.icon}}
{{#if this.title}}<span class="me-1">{{/if}}
{{~icon_img this.icon~}}
{{#if this.title}}</span>{{/if}}
{{/if}}
{{~this.title~}}
</a>
{{~#if this.submenu~}}
<div class="dropdown-menu dropdown-menu-end" data-bs-popper="static">
{{~#each this.submenu~}}
{{#if (or (or this.title this.icon) this.image)}}
<a class="dropdown-item" href="{{this.link}}">
{{~#if this.image~}}
<span {{~#if this.title}} class="me-1"{{/if}}>
{{~#if (eq ../this.size 'sm')}}
<img width=16 height=16 src="{{this.image}}">
{{~else~}}
<img width=24 height=24 src="{{this.image}}">
{{~/if~}}
</span>
{{~/if~}}
{{#if this.icon}}
{{#if this.title}}<span class="me-1">{{/if}}
{{~icon_img this.icon~}}
{{#if this.title}}</span>{{/if}}
{{/if}}
{{~this.title~}}
</a>
{{~/if~}}
{{~/each~}}
</div>
</div>
<div class="collapse navbar-collapse" id="navbar-menu">
<ul class="navbar-nav ms-auto">
{{~#each (to_array menu_item)~}}
{{~#if (or (eq (typeof this) 'object') (and (eq (typeof this) 'string') (starts_with this '{')))}}
{{~#with (parse_json this)}}
{{#if (or (or this.title this.icon) this.image)}}
<li class="nav-item{{#if this.submenu}} dropdown{{/if}}">
<a class="nav-link {{#if this.submenu}}dropdown-toggle{{/if}}" href="{{#if this.link}}{{this.link}}{{else}}#{{/if}}"
{{~#if this.submenu}} data-bs-toggle="dropdown" data-bs-auto-close="outside" {{/if~}}
role="button"
>
{{~#if this.image~}}
<span {{~#if this.title}} class="me-1"{{/if}}>
{{~#if (eq this.size 'sm')}}
<img width=16 height=16 src="{{this.image}}">
{{~else~}}
<img width=24 height=24 src="{{this.image}}">
{{~/if~}}
</span>
{{~/if~}}
{{#if this.icon}}
{{#if this.title}}<span class="me-1">{{/if}}
{{~icon_img this.icon~}}
{{#if this.title}}</span>{{/if}}
{{/if}}
</li>
{{/if}}
{{/with}}
{{~else}}
{{~#if (gt (len this) 0)~}}
<li class="nav-item">
<a class="nav-link text-capitalize" href="{{this}}.sql">{{this}}</a>
{{~this.title~}}
</a>
{{~#if this.submenu~}}
<div class="dropdown-menu dropdown-menu-end" data-bs-popper="static">
{{~#each this.submenu~}}
{{#if (or (or this.title this.icon) this.image)}}
<a class="dropdown-item" href="{{this.link}}">
{{~#if this.image~}}
<span {{~#if this.title}} class="me-1"{{/if}}>
{{~#if (eq ../this.size 'sm')}}
<img width=16 height=16 src="{{this.image}}">
{{~else~}}
<img width=24 height=24 src="{{this.image}}">
{{~/if~}}
</span>
{{~/if~}}
{{#if this.icon}}
{{#if this.title}}<span class="me-1">{{/if}}
{{~icon_img this.icon~}}
{{#if this.title}}</span>{{/if}}
{{/if}}
{{~this.title~}}
</a>
{{~/if~}}
{{~/each~}}
</div>
{{/if}}
</li>
{{~/if~}}
{{/if}}
{{/with}}
{{~else}}
{{~#if (gt (len this) 0)~}}
<li class="nav-item">
<a class="nav-link text-capitalize" href="{{this}}.sql">{{this}}</a>
</li>
{{~/if~}}
{{~/each}}
</ul>
{{#if search_target}}
<form class="d-flex" role="search" action="{{search_target}}">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search" name="search" value="{{search_value}}">
<button class="btn btn-outline-success" type="submit">Search</button>
</form>
{{/if}}
</div>
{{~/if~}}
{{~/each}}
</ul>
{{#if search_target}}
<form class="d-flex" role="search" action="{{search_target}}">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search" name="search" value="{{search_value}}">
<button class="btn btn-outline-success" type="submit">Search</button>
</form>
{{/if}}
</div>
</nav>
</header>
Expand Down
5 changes: 5 additions & 0 deletions tests/end-to-end/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
60 changes: 60 additions & 0 deletions tests/end-to-end/official-site.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { test, expect } from '@playwright/test';

const BASE = 'http://localhost:8080/';

test('Open documentation', async ({ page }) => {
await page.goto(BASE);

// Expect a title "to contain" a substring.
await expect(page).toHaveTitle("SQLPage");

// open the submenu
await page.getByText('Documentation', { exact: true }).first().click();
await page.getByText('All Components').click();
const components = ['form', 'map', 'chart', 'button'];
for (const component of components) {
await expect(page.getByRole('link', { name: component }).first()).toBeVisible();
}
});

test('chart', async ({ page }) => {
await page.goto(BASE + '/documentation.sql?component=chart#component');
await expect(page.getByText('Loading...')).not.toBeVisible();
await expect(page.locator('.apexcharts-canvas').first()).toBeVisible();
});

test('map', async ({ page }) => {
await page.goto(BASE + '/documentation.sql?component=map#component');
await expect(page.getByText('Loading...')).not.toBeVisible();
await expect(page.locator('.leaflet-marker-icon').first()).toBeVisible();
});

test('form example', async ({ page }) => {
await page.goto(BASE + '/examples/multistep-form');
// Single selection matching the value or label
await page.getByLabel('From').selectOption('Paris');
await page.getByText('Next').click();
await page.getByLabel(/\bTo\b/).selectOption('Mexico');
await page.getByText('Next').click();
await page.getByLabel('Number of Adults').fill('1');
await page.getByText('Next').click();
await page.getByLabel('Passenger 1 (adult)').fill('John Doe');
await page.getByText('Book the flight').click();
await expect(page.getByText('John Doe').first()).toBeVisible();
});

test('File upload', async ({ page }) => {
await page.goto(BASE);
await page.getByText('Examples', { exact: true }).click();
await page.getByText('File uploads').click();
const my_svg = '<svg><text y="20">Hello World</text></svg>';
// @ts-ignore
const buffer = Buffer.from(my_svg);
await page.getByLabel('Picture').setInputFiles({
name: 'small.svg',
mimeType: 'image/svg+xml',
buffer,
});
await page.getByRole('button', { name: 'Upload picture' }).click();
await expect(page.locator('img[src^=data]').first().getAttribute('src')).resolves.toBe('data:image/svg+xml;base64,' + buffer.toString('base64'));
});
Loading

0 comments on commit 6cc4cce

Please sign in to comment.