diff --git a/components/AuditForm.vue b/components/AuditForm.vue index 20a6f27..e742b37 100644 --- a/components/AuditForm.vue +++ b/components/AuditForm.vue @@ -59,7 +59,7 @@ if (baseAuditId) { } else { setValues({ pages: baseAudit.config.noAxe - ? [{ selector: '', url: '' }] + ? [{ selector: '', endSelector: '', url: '' }] : baseAudit.config.pages, title: baseAudit.config.title, project: baseAudit.projects?.id, @@ -267,7 +267,7 @@ const onAuditProcessingDialogClose = (resetAuditForm: boolean = true) => {
- + { selector allowed. If empty whole document will be tested.
+
+ + + + Sometimes dynamic page is not fully loaded when automatic + tests are conducted. To make sure all elements on the page are + available, provide the element at the end of the page, use + .class or #id to choose selector, just one selector allowed. + +
{ :pt="{ icon: { 'aria-hidden': true }, }" - @click="pushPage({ url: '', selector: '' })" + @click="pushPage({ url: '', selector: '', endSelector: '' })" /> diff --git a/pages/audit/report/[id].vue b/pages/audit/report/[id].vue index c7f3f79..674bc8a 100644 --- a/pages/audit/report/[id].vue +++ b/pages/audit/report/[id].vue @@ -174,6 +174,14 @@ const completeReport = async () => { {{ page.selector }} + diff --git a/server/axe-runner/core.ts b/server/axe-runner/core.ts index 483ddf5..aebc541 100644 --- a/server/axe-runner/core.ts +++ b/server/axe-runner/core.ts @@ -60,7 +60,7 @@ export const doTest = async function ( await page.setViewportSize(size) - for await (const { url, selector } of pages) { + for await (const { url, selector, endSelector } of pages) { let result: AxeResults | undefined const errors: ResultError[] = [] @@ -73,6 +73,24 @@ export const doTest = async function ( const axe = await new AxeBuilder({ page }) + if (endSelector) { + await page + .locator(endSelector) + .waitFor() + .then(async () => { + await page.evaluate(() => + window.scrollTo(0, document.body.scrollHeight) + ) + }) + .catch(() => + errors.push({ + url, + message: + 'Selector of element at the end of the page does not exist.', + }) + ) + } + if (selector) { await page .locator(selector) @@ -95,7 +113,9 @@ export const doTest = async function ( result = await axe.analyze() } - results.push(parseResults(auditId, size, errors, result, selector)) + results.push( + parseResults(auditId, size, errors, result, selector, endSelector) + ) } if (process.env.dev) { diff --git a/server/axe-runner/utils.ts b/server/axe-runner/utils.ts index dde3cab..1c5aac3 100644 --- a/server/axe-runner/utils.ts +++ b/server/axe-runner/utils.ts @@ -62,11 +62,13 @@ export const parseResults = function ( size: ViewportSize, errors?: ResultError[], results?: AxeResults, - selector?: string + selector?: string, + endSelector?: string ): TestResult { return { audit_id: auditId, selector: selector || null, + end_selector: endSelector || null, size: `${size.width},${size.height}`, results: results ? trimResults(results) : null, errors, diff --git a/sql/02_tables/05_axe.sql b/sql/02_tables/05_axe.sql index 0d77dc5..bc1cd8e 100644 --- a/sql/02_tables/05_axe.sql +++ b/sql/02_tables/05_axe.sql @@ -4,6 +4,7 @@ create table axe ( created_at timestamp default current_timestamp not null, audit_id serial references public.audits on delete cascade not null, selector text, + end_selector text, size text, results JSONB not null DEFAULT '{}'::jsonb, form_data JSONB not null DEFAULT '{}'::jsonb, diff --git a/types/audit.ts b/types/audit.ts index d649b40..874ce7a 100644 --- a/types/audit.ts +++ b/types/audit.ts @@ -3,6 +3,7 @@ import type { auditTemplate } from '~/data/auditTemplate' export interface Page { selector?: string | undefined + endSelector?: string | undefined url: string } diff --git a/types/axe-runner.ts b/types/axe-runner.ts index e95265a..24a4d07 100644 --- a/types/axe-runner.ts +++ b/types/axe-runner.ts @@ -20,6 +20,7 @@ export type ResultError = { export interface TestResult { audit_id: string selector: string | null + end_selector: string | null size: string results: TrimmedResults | null errors?: ResultError[] @@ -32,6 +33,7 @@ export type BasicAuth = { export type Page = { selector?: string + endSelector?: string url: string } diff --git a/types/supabase-latest.ts b/types/supabase-latest.ts index cc0ed97..95d6765 100644 --- a/types/supabase-latest.ts +++ b/types/supabase-latest.ts @@ -70,6 +70,7 @@ export interface Database { id: number results: Json | null selector: string | null + end_selector: string | null size: string | null } Insert: { @@ -80,6 +81,7 @@ export interface Database { id?: number results?: Json | null selector?: string | null + end_selector?: string | null size?: string | null } Update: { @@ -90,6 +92,7 @@ export interface Database { id?: number results?: Json | null selector?: string | null + end_selector?: string | null size?: string | null } Relationships: [ diff --git a/types/supabase.ts b/types/supabase.ts index 902f6e3..c8f346a 100644 --- a/types/supabase.ts +++ b/types/supabase.ts @@ -106,6 +106,7 @@ export interface Database { results: Results errors: Json[] | null selector: string | null + end_selector: string | null size: string | null form_data: FormData | null } @@ -115,6 +116,7 @@ export interface Database { id?: number results?: Results selector?: string | null + end_selector?: string | null size?: string | null form_data?: FormData | null } @@ -124,6 +126,7 @@ export interface Database { id?: number results?: Results selector?: string | null + end_selector?: string | null size?: string | null form_data?: FormData | null } diff --git a/validation/schema.ts b/validation/schema.ts index dc0cfde..b7e9125 100644 --- a/validation/schema.ts +++ b/validation/schema.ts @@ -33,10 +33,12 @@ export const auditFormSchema = object({ object().shape({ url: string(), selector: string(), + endSelector: string(), }) ) .default([ { + endSelector: '', selector: '', url: '', }, @@ -48,12 +50,14 @@ export const auditFormSchema = object({ .shape({ url: string().url().required(), selector: string(), + endSelector: string(), }) .test('isUnique', `The entry is not unique`, function (currentPage) { const pages = this.parent const count = pages.filter( (page: Page) => page.selector === currentPage.selector && + page.endSelector === currentPage.endSelector && page.url === currentPage.url ).length return count <= 1 @@ -63,6 +67,7 @@ export const auditFormSchema = object({ .default([ { selector: '', + endSelector: '', url: '', }, ])