683 lines
29 KiB
JavaScript
683 lines
29 KiB
JavaScript
// Only remaining criteria. Closed A/B and C37 are not repeated.
|
|
const {
|
|
start,
|
|
DIR,
|
|
assert,
|
|
fs,
|
|
path,
|
|
sanitized,
|
|
} = require('./portal-visual-qa-lib.cjs');
|
|
const mode = process.argv[2] || 'land';
|
|
void (async () => {
|
|
const q = await start('revision-' + mode + '-chromium-20261011');
|
|
let page, ctx;
|
|
async function actor(role) {
|
|
if (ctx) await ctx.close();
|
|
({ ctx, page } = await q.context(role));
|
|
if (role !== 'public') await q.login(page, role);
|
|
}
|
|
async function open(route) {
|
|
const r = await q.open(page, route);
|
|
const html = await r.text();
|
|
const attr = html.match(/data-page="([^"]+)"/);
|
|
const raw = attr?.[1].startsWith('{')
|
|
? attr[1]
|
|
.replace(/"/g, '"')
|
|
.replace(/&/g, '&')
|
|
.replace(/'/g, "'")
|
|
: html.match(
|
|
/<script[^>]*type="application\/json"[^>]*>([\s\S]*?)<\/script>/,
|
|
)?.[1];
|
|
assert(raw, 'Inertia props');
|
|
return JSON.parse(raw).props;
|
|
}
|
|
async function a11y(name) {
|
|
await page.addScriptTag({
|
|
path: path.join(DIR, 'qa-a11y-toolkit/package/axe.min.js'),
|
|
});
|
|
const findings = await page.evaluate(async () => {
|
|
const result = await axe.run(document, {
|
|
runOnly: {
|
|
type: 'tag',
|
|
values: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'],
|
|
},
|
|
});
|
|
return result.violations.map((v) => ({
|
|
id: v.id,
|
|
impact: v.impact,
|
|
nodes: v.nodes.map((n) => ({
|
|
target: n.target,
|
|
summary: n.failureSummary,
|
|
})),
|
|
}));
|
|
});
|
|
(q.proof.accessibility ||= []).push({
|
|
route: new URL(page.url()).pathname,
|
|
name,
|
|
violations: findings,
|
|
});
|
|
q.save();
|
|
return findings;
|
|
}
|
|
try {
|
|
if (mode === 'land') {
|
|
await actor('promoter');
|
|
const id = '01a12568-c330-73ec-a1ac-543ac0116a26';
|
|
const route = '/investidor/terra-duat/' + id;
|
|
let p = await open(route);
|
|
q.proof.case = {
|
|
id,
|
|
status_before: p.case.status,
|
|
version_before: p.case.version,
|
|
country: p.case.payload.country,
|
|
origin: p.case.payload.declared_origin_country_code,
|
|
};
|
|
assert.equal(p.case.payload.country, 'MZ');
|
|
assert.equal(p.case.payload.declared_origin_country_code, 'PT');
|
|
if (p.case.status === 'draft') {
|
|
q.phase('Land: support conversion from existing draft, UI');
|
|
await open(route + '/editar');
|
|
await page
|
|
.getByLabel(/Pretende apoio para/)
|
|
.selectOption('investment');
|
|
await page
|
|
.getByLabel(/Descrição do pedido/)
|
|
.fill(
|
|
'Ensaio sintético QA: orientação interna e preservação da origem declarada.',
|
|
);
|
|
await page
|
|
.getByLabel(/Categoria do requerente/)
|
|
.selectOption('individual');
|
|
await page
|
|
.getByLabel(/Nome ou denominação/)
|
|
.fill('Promotor sintético QA21 — ensaio de apoio');
|
|
await page.getByLabel(/Canal preferido/).selectOption('email');
|
|
await page
|
|
.getByRole('button', { name: 'Continuar', exact: true })
|
|
.click();
|
|
await page
|
|
.getByLabel(/Situação da localização/)
|
|
.selectOption('location_support');
|
|
await page
|
|
.locator('#land-field-purpose')
|
|
.fill(
|
|
'Preparação de investimento sintético em QA; sem decisão externa.',
|
|
);
|
|
await page
|
|
.getByRole('button', { name: 'Anterior', exact: true })
|
|
.click();
|
|
for (let i = 0; i < 3; i++)
|
|
await page
|
|
.getByRole('button', { name: 'Continuar', exact: true })
|
|
.click();
|
|
await page
|
|
.getByLabel(/Confirmo que as respostas correspondem/)
|
|
.check();
|
|
await page
|
|
.getByLabel(/Autorizo o contacto para responder/)
|
|
.check();
|
|
await a11y('land-support-final-step');
|
|
const pending = page.waitForResponse(
|
|
(r) =>
|
|
new URL(r.url()).pathname ===
|
|
route + '/solicitar-apoio' &&
|
|
r.request().method() === 'POST',
|
|
);
|
|
await page
|
|
.getByRole('button', {
|
|
name: 'Solicitar apoio',
|
|
exact: true,
|
|
})
|
|
.click();
|
|
const response = await pending;
|
|
q.proof.support_status = response.status();
|
|
await page.waitForLoadState('networkidle');
|
|
await page.waitForFunction(
|
|
() =>
|
|
!Array.from(document.querySelectorAll('button')).some(
|
|
(b) =>
|
|
b.textContent.trim() === 'Solicitar apoio' &&
|
|
b.disabled,
|
|
),
|
|
);
|
|
if (
|
|
![302, 303, 202].includes(response.status()) ||
|
|
new URL(page.url()).pathname.endsWith('/editar')
|
|
) {
|
|
q.proof.field_errors = await page
|
|
.locator('[role="alert"]')
|
|
.allTextContents();
|
|
await q.shot(page, 'support-errors');
|
|
throw Error(
|
|
'Support conversion not demonstrated; inspect field errors and response status',
|
|
);
|
|
}
|
|
}
|
|
p = await open(route);
|
|
assert.equal(p.case.status, 'support_requested');
|
|
assert(p.supportTicket?.reference);
|
|
q.proof.case.support_reference = p.supportTicket.reference;
|
|
await q.shot(page, 'land-support-recovered');
|
|
await a11y('land-support-read');
|
|
q.phase('Land: new session recovery and permission');
|
|
await actor('promoter');
|
|
p = await open(route);
|
|
assert.equal(p.case.payload.declared_origin_country_code, 'PT');
|
|
assert.equal(p.case.status, 'support_requested');
|
|
await actor('outsider');
|
|
q.proof.outsider_status = (await q.open(page, route)).status();
|
|
assert([403, 404].includes(q.proof.outsider_status));
|
|
q.phase('Land: manager recovery and origin statistics');
|
|
await actor('manager');
|
|
p = await open('/management/terra-duat/' + id);
|
|
assert.equal(p.case.payload.country, 'MZ');
|
|
assert.equal(p.case.payload.declared_origin_country_code, 'PT');
|
|
await q.shot(page, 'manager-support-country-origin');
|
|
const stats = await open('/management/interests?origin=PT');
|
|
q.proof.management_props = Object.keys(stats);
|
|
q.proof.statistics =
|
|
stats.declaredOriginStatistics ||
|
|
stats.originStatistics ||
|
|
null;
|
|
await q.shot(page, 'support-statistics');
|
|
await a11y('management-statistics');
|
|
q.proof.criteria = {
|
|
support_ui: true,
|
|
country_origin_preserved: true,
|
|
new_session: true,
|
|
manager_recovery: true,
|
|
outsider_denied: true,
|
|
idempotent_conversion:
|
|
'Separate guarded persistence/replay proof required; no second UI submission',
|
|
};
|
|
} else if (mode === 'land-errors') {
|
|
await actor('promoter');
|
|
q.phase('Land: incomplete draft and accessible support errors');
|
|
let route = q.prior.fixture?.route;
|
|
if (!route) {
|
|
await open('/investidor/terra-duat/novo');
|
|
await page
|
|
.getByLabel(/Nome ou denominação/)
|
|
.fill('Ensaio sintético QA — erros acessíveis');
|
|
const navigation = page.waitForURL((u) =>
|
|
/^\/investidor\/terra-duat\/[a-f0-9-]+$/.test(u.pathname),
|
|
);
|
|
await page
|
|
.getByRole('button', {
|
|
name: 'Guardar rascunho',
|
|
exact: true,
|
|
})
|
|
.click();
|
|
await navigation;
|
|
route = new URL(page.url()).pathname;
|
|
q.proof.fixture = { route };
|
|
q.save();
|
|
} else q.proof.fixture = { route };
|
|
await open(route + '/editar');
|
|
for (let i = 0; i < 3; i++)
|
|
await page
|
|
.getByRole('button', { name: 'Continuar', exact: true })
|
|
.click();
|
|
await page
|
|
.getByRole('button', { name: 'Solicitar apoio', exact: true })
|
|
.click();
|
|
await page.waitForFunction(
|
|
() => document.activeElement?.id === 'land-field-support_kind',
|
|
);
|
|
assert.equal(
|
|
await page
|
|
.locator('#land-field-support_kind')
|
|
.getAttribute('aria-describedby'),
|
|
'land-error-support_kind',
|
|
);
|
|
assert(await page.locator('#land-error-support_kind').isVisible());
|
|
q.proof.criteria = {
|
|
incomplete_draft_recoverable: true,
|
|
invalid_request_not_submitted: true,
|
|
first_error_focused: true,
|
|
field_error_associated: true,
|
|
summary_names_fields: await page
|
|
.locator('#land-error-summary')
|
|
.innerText(),
|
|
};
|
|
await a11y('land-invalid-support');
|
|
await q.shot(page, 'land-errors-focused');
|
|
const stored = await open(route);
|
|
assert.equal(stored.case.status, 'draft');
|
|
q.proof.criteria.invalid_remains_draft = true;
|
|
} else if (mode === 'land-replay') {
|
|
await actor('promoter');
|
|
q.phase('Land: API repetition of already submitted support');
|
|
const route =
|
|
'/investidor/terra-duat/01a12568-c330-73ec-a1ac-543ac0116a26';
|
|
const before = await open(route);
|
|
assert.equal(before.case.status, 'support_requested');
|
|
const errorsFixture = JSON.parse(
|
|
fs.readFileSync(
|
|
path.join(
|
|
DIR,
|
|
'revision-land-errors-chromium-20261011.json',
|
|
),
|
|
'utf8',
|
|
),
|
|
);
|
|
await open(errorsFixture.fixture.route + '/editar');
|
|
assert(
|
|
await page
|
|
.locator('p')
|
|
.filter({ hasText: /^País do requerente \(opcional\)$/ })
|
|
.isVisible(),
|
|
);
|
|
assert(
|
|
await page
|
|
.getByLabel('País do requerente (opcional)', {
|
|
exact: true,
|
|
})
|
|
.isVisible(),
|
|
);
|
|
assert(
|
|
await page
|
|
.getByLabel(
|
|
'País de origem declarado da solicitação (opcional)',
|
|
{ exact: true },
|
|
)
|
|
.isVisible(),
|
|
);
|
|
await a11y('land-country-labels');
|
|
await q.shot(page, 'land-country-origin-distinct');
|
|
const status = await page.evaluate(
|
|
async ({ route, record }) => {
|
|
const token = document.cookie
|
|
.split('; ')
|
|
.find((c) => c.startsWith('XSRF-TOKEN='));
|
|
return (
|
|
await fetch(route + '/solicitar-apoio', {
|
|
method: 'POST',
|
|
headers: {
|
|
Accept: 'application/json',
|
|
'Content-Type': 'application/json',
|
|
'X-XSRF-TOKEN': token
|
|
? decodeURIComponent(token.slice(11))
|
|
: '',
|
|
},
|
|
body: JSON.stringify({
|
|
version: record.version,
|
|
payload: record.payload,
|
|
}),
|
|
})
|
|
).status;
|
|
},
|
|
{ route, record: before.case },
|
|
);
|
|
assert.equal(status, 409);
|
|
const after = await open(route);
|
|
assert.equal(after.case.version, before.case.version);
|
|
assert.equal(
|
|
after.supportTicket.reference,
|
|
before.supportTicket.reference,
|
|
);
|
|
q.proof.criteria = {
|
|
api_replay_status: status,
|
|
version_unchanged: true,
|
|
support_reference_unchanged: true,
|
|
visual_already_proved: 'revision-land-chromium-20261011.json',
|
|
};
|
|
const master = await open('/investidor/profile');
|
|
assert(master.organizationCountryAvailable);
|
|
q.proof.country_master_current_build = {
|
|
available: master.organizationCountryAvailable,
|
|
represented_count: master.organizations.length,
|
|
values_recovered: master.organizations.map((o) => ({
|
|
id: o.id,
|
|
country: o.country,
|
|
version: o.version,
|
|
})),
|
|
};
|
|
} else if (mode === 'zones-map') {
|
|
q.proof.consumers = [];
|
|
for (const role of ['public', 'manager', 'geo']) {
|
|
await actor(role);
|
|
const route =
|
|
role === 'public'
|
|
? '/zonas/ed4d2fba-95d5-42e3-8ca7-1f520de1036a'
|
|
: '/management/investment/zones/ed4d2fba-95d5-42e3-8ca7-1f520de1036a';
|
|
q.phase('Zone own geometry map: ' + role);
|
|
if (role === 'manager') {
|
|
const denied = await q.open(page, route);
|
|
assert.equal(denied.status(), 403);
|
|
assert(
|
|
(await denied.text()).includes('Configure e confirme'),
|
|
);
|
|
q.proof.consumers.push({
|
|
route,
|
|
role,
|
|
status: 403,
|
|
reason: 'Existing QA14 lacks confirmed MFA required by RequireZoneMfa; no privileges or MFA changed.',
|
|
});
|
|
continue;
|
|
}
|
|
if (role === 'geo') {
|
|
const props = await open(route);
|
|
assert.equal(props.management, true);
|
|
const region = page.getByRole('region', {
|
|
name: 'Localização do investimento',
|
|
exact: true,
|
|
});
|
|
const province = region.getByLabel('Província', {
|
|
exact: true,
|
|
});
|
|
await province
|
|
.locator(
|
|
'option[value="059b02ca-7095-577a-8705-4d4c46896747"]',
|
|
)
|
|
.waitFor({ state: 'attached' });
|
|
const pending = page.waitForResponse(
|
|
(r) =>
|
|
new URL(r.url()).pathname ===
|
|
'/management/territory-geometries',
|
|
);
|
|
await province.selectOption(
|
|
'059b02ca-7095-577a-8705-4d4c46896747',
|
|
);
|
|
const response = await pending;
|
|
assert.equal(response.status(), 200);
|
|
const data = await response.json();
|
|
assert.equal(data.selected_geometry_available, true);
|
|
await page
|
|
.getByRole('region', {
|
|
name: 'Mapa dos limites administrativos',
|
|
exact: true,
|
|
})
|
|
.last()
|
|
.waitFor();
|
|
await page.setViewportSize({ width: 390, height: 844 });
|
|
const overflow = await page.evaluate(
|
|
() => document.documentElement.scrollWidth > innerWidth,
|
|
);
|
|
q.proof.consumers.push({
|
|
route,
|
|
role,
|
|
api_status: 200,
|
|
administrative_geometry_available: true,
|
|
administrative_geometry_count:
|
|
data.context_geometry_count,
|
|
selection_only_not_saved: true,
|
|
mobile390_overflow: overflow,
|
|
own_zone_map_not_present_in_editor: true,
|
|
});
|
|
await a11y('zone-territory-manager');
|
|
await q.shot(page, 'zone-territory-manager');
|
|
continue;
|
|
}
|
|
await open(route);
|
|
const pending = page.waitForResponse(
|
|
(r) =>
|
|
new URL(r.url()).pathname ===
|
|
'/api/v2/investment/zone-map',
|
|
);
|
|
await page
|
|
.getByRole('button', {
|
|
name: 'Consultar mapa',
|
|
exact: true,
|
|
})
|
|
.focus();
|
|
await page.keyboard.press('Enter');
|
|
const response = await pending;
|
|
assert.equal(response.status(), 200);
|
|
const data = await response.json();
|
|
assert(Array.isArray(data.features));
|
|
if (data.features.length)
|
|
await page
|
|
.getByRole('img', {
|
|
name: 'Esquema das geometrias autorizadas da zona',
|
|
})
|
|
.waitFor();
|
|
else
|
|
await page
|
|
.getByText(
|
|
'Não existe geometria autorizada para divulgação nesta ficha.',
|
|
{ exact: true },
|
|
)
|
|
.waitFor();
|
|
await page.setViewportSize({ width: 390, height: 844 });
|
|
const overflow = await page.evaluate(
|
|
() => document.documentElement.scrollWidth > innerWidth,
|
|
);
|
|
assert.equal(
|
|
overflow,
|
|
false,
|
|
'The map must fit the 390px viewport',
|
|
);
|
|
q.proof.consumers.push({
|
|
route,
|
|
role,
|
|
api_status: 200,
|
|
own_geometry_count: data.features.length,
|
|
keyboard_action: true,
|
|
empty_state_visible: data.features.length === 0,
|
|
mobile390_overflow: overflow,
|
|
not_administrative_parcel_equivalence: true,
|
|
});
|
|
await a11y('zone-own-map-' + role);
|
|
await q.shot(page, 'zone-map-' + role);
|
|
}
|
|
} else if (mode === 'zones') {
|
|
await actor('public');
|
|
q.phase('Zones: public selectors and geometry consumer');
|
|
const p = await open('/zonas');
|
|
q.proof.props = Object.keys(p);
|
|
q.proof.selector_names = await page
|
|
.locator('select')
|
|
.evaluateAll((nodes) =>
|
|
nodes.map((n) => ({
|
|
name: n.name,
|
|
id: n.id,
|
|
label: n.labels?.[0]?.textContent?.trim(),
|
|
options: n.options.length,
|
|
})),
|
|
);
|
|
await a11y('zone-search');
|
|
await q.shot(page, 'zone-search');
|
|
q.proof.records =
|
|
p.records?.data?.map((r) => ({ id: r.id, name: r.name })) || [];
|
|
const province = page.getByLabel('Província', { exact: true });
|
|
await province.selectOption('059b02ca-7095-577a-8705-4d4c46896747');
|
|
await page.waitForFunction(
|
|
() =>
|
|
document.querySelector('input[name="province_id"]')
|
|
.value === '059b02ca-7095-577a-8705-4d4c46896747',
|
|
);
|
|
const navigation = page.waitForURL(
|
|
(u) =>
|
|
u.searchParams.get('province_id') ===
|
|
'059b02ca-7095-577a-8705-4d4c46896747',
|
|
);
|
|
await page
|
|
.getByRole('button', { name: 'Pesquisar', exact: true })
|
|
.click();
|
|
await navigation;
|
|
await page.waitForLoadState('networkidle');
|
|
assert(
|
|
new URL(page.url()).searchParams.get('province_id') ===
|
|
'059b02ca-7095-577a-8705-4d4c46896747',
|
|
);
|
|
await q.shot(page, 'zone-filter-cabo-delgado');
|
|
await a11y('zone-filtered');
|
|
q.proof.criteria = {
|
|
public_filter: true,
|
|
selectors_filled: true,
|
|
map_functional: false,
|
|
note: 'Inspect map presentation separately; a filter response is not geometry coverage',
|
|
};
|
|
} else if (mode === 'library') {
|
|
await actor('manager');
|
|
q.phase(
|
|
'Library: locate synthetic published resource without reimport',
|
|
);
|
|
const p = await open('/management/portal-services');
|
|
q.proof.synthetic_candidates = p.resources
|
|
.filter(
|
|
(r) =>
|
|
r.published &&
|
|
/qa|sint[eé]tic|fixture|demonstra/i.test(r.title),
|
|
)
|
|
.map((r) => ({
|
|
id: r.id,
|
|
title: r.title,
|
|
published: r.published,
|
|
document: r.document_id,
|
|
}));
|
|
await a11y('library-management');
|
|
await q.shot(page, 'library-management');
|
|
const legacy = p.resources.find(
|
|
(r) =>
|
|
r.published &&
|
|
/agroneg[oó]cio/i.test(r.title) &&
|
|
r.source_url?.startsWith('https://investemoz.tempo.co.mz/'),
|
|
);
|
|
const selected =
|
|
q.prior.selected ||
|
|
q.proof.synthetic_candidates[0] ||
|
|
(legacy && {
|
|
id: legacy.id,
|
|
title: legacy.title,
|
|
published: true,
|
|
document: legacy.document_id,
|
|
scope: 'Already disclosed legacy Agronegocio QA; temporary withdrawal with restoration',
|
|
});
|
|
assert(
|
|
selected,
|
|
'No published synthetic resource; do not withdraw arbitrary content',
|
|
);
|
|
q.proof.selected = selected;
|
|
const resource = p.resources.find((r) => r.id === selected.id);
|
|
assert(resource);
|
|
const doc = p.documents.find((d) => d.id === resource.document_id);
|
|
q.proof.original = {
|
|
id: doc.id,
|
|
sha256: doc.sha256,
|
|
scan: doc.malware_scan_status,
|
|
};
|
|
assert.equal(doc.malware_scan_status, 'clean');
|
|
const publicContext = await q.context('public');
|
|
const publicPage = publicContext.page;
|
|
const url = '/documentos/' + resource.id + '/download';
|
|
const before = await publicPage.request.get(
|
|
'http://127.0.0.1:18097' + url,
|
|
);
|
|
assert.equal(before.status(), 200);
|
|
q.proof.public_hash_before = require('node:crypto')
|
|
.createHash('sha256')
|
|
.update(await before.body())
|
|
.digest('hex');
|
|
const article = page.locator('article').filter({
|
|
has: page.getByRole('heading', {
|
|
name: resource.title + ' · Publicado',
|
|
exact: true,
|
|
}),
|
|
});
|
|
const pending = page.waitForResponse(
|
|
(r) =>
|
|
new URL(r.url()).pathname ===
|
|
'/management/portal-services/resources/' +
|
|
resource.id && r.request().method() === 'POST',
|
|
);
|
|
await article
|
|
.getByRole('button', {
|
|
name: 'Retirar da publicação',
|
|
exact: true,
|
|
})
|
|
.click();
|
|
assert([302, 303].includes((await pending).status()));
|
|
await page.waitForLoadState('networkidle');
|
|
q.proof.revoked_same_session = (
|
|
await publicPage.request.get('http://127.0.0.1:18097' + url)
|
|
).status();
|
|
assert([403, 404].includes(q.proof.revoked_same_session));
|
|
await publicContext.ctx.close();
|
|
const newer = await q.context('public');
|
|
q.proof.revoked_new_session = (
|
|
await newer.page.request.get('http://127.0.0.1:18097' + url)
|
|
).status();
|
|
assert([403, 404].includes(q.proof.revoked_new_session));
|
|
await newer.ctx.close();
|
|
const original = await page.request.get(
|
|
'http://127.0.0.1:18097/management/documents/' +
|
|
doc.id +
|
|
'/download',
|
|
);
|
|
assert.equal(original.status(), 200);
|
|
assert.equal(
|
|
require('node:crypto')
|
|
.createHash('sha256')
|
|
.update(await original.body())
|
|
.digest('hex'),
|
|
q.proof.public_hash_before,
|
|
);
|
|
q.proof.original_preserved = true;
|
|
await q.shot(page, 'library-withdrawn');
|
|
const withdrawn = page.locator('article').filter({
|
|
has: page.getByRole('heading', {
|
|
name: resource.title + ' · Rascunho',
|
|
exact: true,
|
|
}),
|
|
});
|
|
const restore = page.waitForResponse(
|
|
(r) =>
|
|
new URL(r.url()).pathname ===
|
|
'/management/portal-services/resources/' +
|
|
resource.id && r.request().method() === 'POST',
|
|
);
|
|
await withdrawn
|
|
.getByRole('button', { name: 'Publicar', exact: true })
|
|
.click();
|
|
assert([302, 303].includes((await restore).status()));
|
|
await page.waitForLoadState('networkidle');
|
|
const restoredPublic = await q.context('public');
|
|
const restored = await restoredPublic.page.request.get(
|
|
'http://127.0.0.1:18097' + url,
|
|
);
|
|
assert.equal(restored.status(), 200);
|
|
assert.equal(
|
|
require('node:crypto')
|
|
.createHash('sha256')
|
|
.update(await restored.body())
|
|
.digest('hex'),
|
|
q.proof.public_hash_before,
|
|
);
|
|
await restoredPublic.ctx.close();
|
|
q.proof.publication_restored = true;
|
|
q.proof.criteria = {
|
|
public_revocation: true,
|
|
same_session_denied: true,
|
|
new_session_denied: true,
|
|
original_preserved: true,
|
|
};
|
|
} else throw Error('Unknown bounded mode');
|
|
q.proof.result = 'passed';
|
|
} catch (e) {
|
|
q.proof.result = 'failed';
|
|
q.proof.error = sanitized(e);
|
|
if (
|
|
page &&
|
|
!['/login', '/two-factor-challenge'].includes(
|
|
new URL(page.url()).pathname,
|
|
)
|
|
)
|
|
await q.shot(page, 'failure').catch(() => {});
|
|
} finally {
|
|
q.proof.finished_at = new Date().toISOString();
|
|
q.save();
|
|
await q.browser.close();
|
|
console.log(
|
|
JSON.stringify({
|
|
result: q.proof.result,
|
|
phase: q.proof.phase,
|
|
error: q.proof.error,
|
|
receipt: q.receipt,
|
|
}),
|
|
);
|
|
if (q.proof.result === 'failed') process.exitCode = 1;
|
|
}
|
|
})();
|