824 lines
33 KiB
JavaScript
824 lines
33 KiB
JavaScript
const {
|
||
start,
|
||
BASE,
|
||
DIR,
|
||
actors: _actors,
|
||
assert,
|
||
fs,
|
||
path,
|
||
sanitized,
|
||
} = require('./portal-visual-qa-lib.cjs');
|
||
void (async () => {
|
||
const q = await start('consumer-workflow-chromium-20261010');
|
||
q.proof.workflow = q.prior.workflow || {
|
||
title: 'QA visual workflow — revisão independente',
|
||
steps: [],
|
||
};
|
||
const w = q.proof.workflow;
|
||
let page, ctx, props;
|
||
async function open(route) {
|
||
const r = await q.open(page, route),
|
||
html = await r.text();
|
||
const a = html.match(/data-page="([^"]+)"/);
|
||
const raw =
|
||
a && a[1].startsWith('{')
|
||
? a[1]
|
||
.replace(/"/g, '"')
|
||
.replace(/&/g, '&')
|
||
.replace(/'/g, "'")
|
||
: html.match(
|
||
/<script[^>]*type="application\/json"[^>]*>([\s\S]*?)<\/script>/,
|
||
)?.[1];
|
||
assert(raw, 'Authorized Inertia page');
|
||
props = JSON.parse(raw).props;
|
||
await page.getByRole('heading').first().waitFor();
|
||
return props;
|
||
}
|
||
async function actor(role) {
|
||
if (ctx) await ctx.close();
|
||
({ page, ctx } = await q.context(role));
|
||
await q.login(page, role);
|
||
}
|
||
const source = fs.readFileSync(
|
||
path.join(
|
||
__dirname,
|
||
'../resources/js/components/opportunity-canonical-fields.tsx',
|
||
),
|
||
'utf8',
|
||
);
|
||
function label(key) {
|
||
return (
|
||
source.match(
|
||
new RegExp('^\\s*' + key + ':\\s*"([^"]+)"', 'm'),
|
||
)?.[1] || key.replaceAll('_', ' ')
|
||
);
|
||
}
|
||
async function scalar(scope, key, field, value) {
|
||
let input = scope.locator('[id=' + JSON.stringify(field) + ']');
|
||
if (!(await input.count()))
|
||
input = scope.getByLabel(
|
||
new RegExp(
|
||
'^' + label(key).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),
|
||
),
|
||
);
|
||
assert.equal(await input.count(), 1, 'One UI field ' + field);
|
||
const tag = await input.evaluate((n) => n.tagName);
|
||
if (tag === 'SELECT') await input.selectOption(String(value));
|
||
else await input.fill(String(value));
|
||
}
|
||
async function fill(scope, key, field, schema, value, options) {
|
||
if (value === null || value === undefined) return;
|
||
if (schema?.$ref)
|
||
schema = options.definitions[schema.$ref.split('/').pop()];
|
||
if (Array.isArray(value)) {
|
||
if (!value.length) return;
|
||
const box = scope
|
||
.locator(
|
||
'fieldset:has(> legend:text-is(' +
|
||
JSON.stringify(label(key)) +
|
||
'))',
|
||
)
|
||
.first();
|
||
assert.equal(await box.count(), 1, 'Array editor ' + field);
|
||
for (let i = 0; i < value.length; i++) {
|
||
const row = box.locator(':scope > div.space-y-2').nth(i);
|
||
if (!(await row.count()))
|
||
await box.locator(':scope > button').click();
|
||
await fill(
|
||
row,
|
||
key,
|
||
field + '.' + i,
|
||
schema.items,
|
||
value[i],
|
||
options,
|
||
);
|
||
}
|
||
return;
|
||
}
|
||
if (typeof value === 'object') {
|
||
for (const [k, v] of Object.entries(value))
|
||
if (schema?.properties?.[k])
|
||
await fill(
|
||
scope,
|
||
k,
|
||
field + '.' + k,
|
||
schema.properties[k],
|
||
v,
|
||
options,
|
||
);
|
||
return;
|
||
}
|
||
await scalar(scope, key, field, value);
|
||
}
|
||
async function transition(action, extra = {}) {
|
||
q.phase(
|
||
'Visual workflow ' + action + (extra.gate ? ' ' + extra.gate : ''),
|
||
);
|
||
await open('/minhas-oportunidades/' + w.id + '/view');
|
||
const before = props.workflowState,
|
||
box = page.getByRole('region', {
|
||
name: 'Workflow interno',
|
||
exact: true,
|
||
});
|
||
assert(
|
||
props.profileActions.includes(action),
|
||
'Action available ' + action,
|
||
);
|
||
await box
|
||
.getByLabel('Fundamentação', { exact: true })
|
||
.fill(
|
||
'Ensaio visual sintético QA: revisão independente, sem decisão externa.',
|
||
);
|
||
if (extra.correction)
|
||
await box
|
||
.getByLabel('Campos ou documentos a corrigir, um por linha', {
|
||
exact: true,
|
||
})
|
||
.fill(extra.correction);
|
||
if (extra.gate) {
|
||
await box.getByLabel(/^Revisão/).selectOption(extra.gate);
|
||
await box.getByLabel(/^Decisão/).selectOption('approved');
|
||
await box
|
||
.getByLabel('QA comprovativo do workflow visual', {
|
||
exact: true,
|
||
})
|
||
.check();
|
||
}
|
||
if (action === 'publish')
|
||
await box.getByLabel(/Confirmei a pré-visualização/).check();
|
||
const names = {
|
||
submit: 'Submeter para validação',
|
||
request_correction: 'Solicitar correções',
|
||
start_review: 'Iniciar validação',
|
||
review_gate: 'Guardar revisão especializada',
|
||
approve: 'Aprovar versão',
|
||
publish: 'Publicar no catálogo público',
|
||
};
|
||
const response = page.waitForResponse(
|
||
(r) =>
|
||
r.request().method() === 'POST' &&
|
||
new URL(r.url()).pathname ===
|
||
'/minhas-oportunidades/' + w.id + '/workflow',
|
||
);
|
||
await box
|
||
.getByRole('button', { name: names[action], exact: true })
|
||
.click();
|
||
const r = await response;
|
||
assert.equal(r.status(), 200, 'Workflow ' + action);
|
||
await page.waitForLoadState('networkidle');
|
||
await open('/minhas-oportunidades/' + w.id + '/view');
|
||
w.steps.push({
|
||
actor: props.auth.user.id,
|
||
action,
|
||
before,
|
||
after: props.workflowState,
|
||
gate: extra.gate,
|
||
});
|
||
q.save();
|
||
await q.shot(
|
||
page,
|
||
action + (extra.gate ? '-' + extra.gate : ''),
|
||
false,
|
||
);
|
||
}
|
||
try {
|
||
q.phase('QA21 dashboard creates incomplete recoverable opportunity');
|
||
await actor('promoter');
|
||
await open('/dashboard');
|
||
await page
|
||
.getByRole('link', { name: 'Nova oportunidade', exact: true })
|
||
.first()
|
||
.click();
|
||
await page.waitForURL(BASE + '/minhas-oportunidades/create');
|
||
if (!w.id) {
|
||
await page
|
||
.getByLabel('Título oficial', { exact: true })
|
||
.first()
|
||
.fill(w.title);
|
||
await page
|
||
.getByLabel('Motivo da alteração', { exact: true })
|
||
.fill(
|
||
'Ensaio visual QA do contrato e revisão por intervenientes independentes.',
|
||
);
|
||
const response = page.waitForResponse(
|
||
(r) =>
|
||
new URL(r.url()).pathname ===
|
||
'/minhas-oportunidades/drafts' &&
|
||
r.request().method() === 'POST',
|
||
);
|
||
await page
|
||
.getByRole('button', { name: 'Guardar rascunho', exact: true })
|
||
.click();
|
||
assert.equal((await response).status(), 201);
|
||
await page.waitForURL(/\/minhas-oportunidades\/\d+\/edit$/);
|
||
w.id = Number(new URL(page.url()).pathname.split('/')[2]);
|
||
w.steps.push({ actor: 21, action: 'create_incomplete_draft' });
|
||
q.save();
|
||
}
|
||
await open('/minhas-oportunidades/' + w.id + '/edit');
|
||
assert.equal(props.auth.user.id, 21);
|
||
if (!w.location || !w.document) {
|
||
q.phase(
|
||
'UI saves per-opportunity location and scanned synthetic document',
|
||
);
|
||
await open('/minhas-oportunidades/' + w.id + '/resources');
|
||
if (!w.location) {
|
||
await page
|
||
.getByRole('combobox', { name: 'Província', exact: true })
|
||
.selectOption({ label: 'Cabo Delgado (02)' })
|
||
.catch(async () => {
|
||
const select = page.getByRole('combobox', {
|
||
name: 'Província',
|
||
exact: true,
|
||
});
|
||
await select
|
||
.locator('option')
|
||
.filter({ hasText: /Cabo Delgado/i })
|
||
.waitFor({ state: 'attached' });
|
||
await select.selectOption(
|
||
await select
|
||
.locator('option')
|
||
.filter({ hasText: /Cabo Delgado/i })
|
||
.getAttribute('value'),
|
||
);
|
||
});
|
||
const district = page.getByRole('combobox', {
|
||
name: 'Distrito',
|
||
exact: true,
|
||
});
|
||
await district
|
||
.locator('option')
|
||
.filter({ hasText: /^ANCUABE(?: \(|$)/i })
|
||
.waitFor({ state: 'attached' });
|
||
await district.selectOption(
|
||
await district
|
||
.locator('option')
|
||
.filter({ hasText: /^ANCUABE(?: \(|$)/i })
|
||
.getAttribute('value'),
|
||
);
|
||
await page
|
||
.getByLabel('Papel da localização', { exact: true })
|
||
.selectOption('headquarters');
|
||
await page
|
||
.getByLabel('Motivo (20–1000 caracteres)', { exact: true })
|
||
.fill(
|
||
'Localização declarada sintética para ensaio QA do workflow.',
|
||
);
|
||
const response = page.waitForResponse(
|
||
(r) =>
|
||
new URL(r.url()).pathname.endsWith(
|
||
'/resources/locations',
|
||
) && r.request().method() === 'POST',
|
||
);
|
||
await page
|
||
.getByRole('button', {
|
||
name: 'Guardar recurso',
|
||
exact: true,
|
||
})
|
||
.click();
|
||
const r = await response;
|
||
assert.equal(r.status(), 201);
|
||
w.location = (await r.json()).id;
|
||
q.save();
|
||
await open('/minhas-oportunidades/' + w.id + '/resources');
|
||
}
|
||
if (!w.document) {
|
||
await page
|
||
.getByRole('button', { name: 'Documentos', exact: true })
|
||
.click();
|
||
await page
|
||
.getByLabel('Título', { exact: true })
|
||
.fill('QA comprovativo do workflow visual');
|
||
await page
|
||
.getByLabel('Categoria documental', { exact: true })
|
||
.fill('qa-synthetic');
|
||
await page
|
||
.getByLabel('Classificação documental', { exact: true })
|
||
.selectOption('restricted');
|
||
await page
|
||
.getByLabel('Âmbito de consulta', { exact: true })
|
||
.selectOption('internal');
|
||
await page
|
||
.getByLabel('Motivo (20–1000 caracteres)', { exact: true })
|
||
.fill(
|
||
'Anexo sintético QA, sem validade oficial ou direitos reais.',
|
||
);
|
||
await page
|
||
.locator('input[type=file]')
|
||
.setInputFiles(
|
||
path.join(DIR, 'synthetic-valid-original.pdf'),
|
||
);
|
||
const response = page.waitForResponse(
|
||
(r) =>
|
||
new URL(r.url()).pathname.endsWith(
|
||
'/resources/documents',
|
||
) && r.request().method() === 'POST',
|
||
);
|
||
await page
|
||
.getByRole('button', {
|
||
name: 'Guardar recurso',
|
||
exact: true,
|
||
})
|
||
.click();
|
||
const r = await response;
|
||
assert.equal(r.status(), 201);
|
||
w.document = (await r.json()).id;
|
||
q.save();
|
||
}
|
||
}
|
||
if (!w.primary_ensured) {
|
||
await open('/minhas-oportunidades/' + w.id + '/resources');
|
||
const location = props.resources.locations.find(
|
||
(r) => r.id === w.location,
|
||
);
|
||
assert(location);
|
||
if (!location.payload.is_primary) {
|
||
await page
|
||
.getByRole('button', {
|
||
name: 'Ver / editar / restaurar',
|
||
exact: true,
|
||
})
|
||
.click();
|
||
await page
|
||
.getByLabel('Localização principal', { exact: true })
|
||
.check();
|
||
await page
|
||
.getByLabel('Motivo (20–1000 caracteres)', { exact: true })
|
||
.fill(
|
||
'Identificação explícita da sede sintética QA para o workflow.',
|
||
);
|
||
const pending = page.waitForResponse(
|
||
(r) =>
|
||
new URL(r.url()).pathname.endsWith(
|
||
'/resources/locations/' + w.location,
|
||
) && r.request().method() === 'POST',
|
||
);
|
||
await page
|
||
.getByRole('button', {
|
||
name: 'Guardar recurso',
|
||
exact: true,
|
||
})
|
||
.click();
|
||
assert.equal((await pending).status(), 200);
|
||
await page.waitForLoadState('networkidle');
|
||
}
|
||
w.primary_ensured = true;
|
||
q.save();
|
||
}
|
||
if (!w.contract_saved) {
|
||
q.phase(
|
||
'UI fills existing canonical editors, preserving resource references',
|
||
);
|
||
await open('/minhas-oportunidades/' + w.id + '/edit');
|
||
const template = JSON.parse(
|
||
fs.readFileSync(
|
||
path.join(DIR, 'consumer-workflow-template-qa.json'),
|
||
'utf8',
|
||
),
|
||
);
|
||
assert(template.synthetic_only);
|
||
const c = {
|
||
...template.canonical,
|
||
official_title: w.title,
|
||
locations: [w.location],
|
||
documents: [w.document],
|
||
evidences: [
|
||
{
|
||
title: 'Comprovativo QA visual',
|
||
evidence_type: 'qa-synthetic',
|
||
document_id: w.document,
|
||
},
|
||
],
|
||
visibility: 'public',
|
||
};
|
||
const contract = page
|
||
.locator('fieldset')
|
||
.filter({
|
||
has: page.locator('legend').filter({
|
||
hasText:
|
||
/^Contrato da oportunidade — investment_project$/,
|
||
}),
|
||
})
|
||
.first();
|
||
const special = [
|
||
'internal_contact_id',
|
||
'sector_id',
|
||
'subsector_id',
|
||
'economic_activity_id',
|
||
];
|
||
for (const key of special)
|
||
await scalar(contract, key, 'canonical.' + key, c[key]);
|
||
for (const key of ['locations', 'documents']) {
|
||
const field = contract.locator(
|
||
'fieldset:has(> legend:text-is(' +
|
||
JSON.stringify(label(key)) +
|
||
'))',
|
||
);
|
||
const id = key === 'locations' ? w.location : w.document;
|
||
const options = props.canonicalOptions.resources[key];
|
||
const index = options.findIndex((r) => r.id === id);
|
||
assert(index >= 0);
|
||
await field.locator('input[type=checkbox]').nth(index).check();
|
||
}
|
||
for (const [key, value] of Object.entries(c)) {
|
||
if (
|
||
[
|
||
'opportunity_category',
|
||
'promoter_id',
|
||
...special,
|
||
'locations',
|
||
'documents',
|
||
].includes(key)
|
||
)
|
||
continue;
|
||
if (props.canonicalOptions.schema.properties[key])
|
||
await fill(
|
||
contract,
|
||
key,
|
||
'canonical.' + key,
|
||
props.canonicalOptions.schema.properties[key],
|
||
value,
|
||
props.canonicalOptions,
|
||
);
|
||
}
|
||
await page
|
||
.getByLabel('Motivo da alteração', { exact: true })
|
||
.fill(
|
||
'Contrato sintético preenchido pelos editores QA para revisão visual.',
|
||
);
|
||
const response = page.waitForResponse(
|
||
(r) =>
|
||
r.request().method() === 'PUT' &&
|
||
new URL(r.url()).pathname.endsWith('/draft'),
|
||
);
|
||
await page
|
||
.getByRole('button', { name: 'Guardar rascunho', exact: true })
|
||
.click();
|
||
assert.equal((await response).status(), 200);
|
||
await page.waitForLoadState('networkidle');
|
||
await open('/minhas-oportunidades/' + w.id + '/edit');
|
||
assert.equal(props.values.canonical.official_title, w.title);
|
||
w.contract_saved = true;
|
||
w.steps.push({ actor: 21, action: 'contract_saved' });
|
||
q.save();
|
||
await q.shot(page, 'contract-saved', false);
|
||
}
|
||
if (!w.current_milestones_saved) {
|
||
await open('/minhas-oportunidades/' + w.id + '/edit');
|
||
for (let i = 0; i < props.values.canonical.milestones.length; i++)
|
||
for (const [key, value] of Object.entries({
|
||
start_date: '2026-11-01',
|
||
end_date: '2026-12-01',
|
||
responsible_name:
|
||
'Responsável sintético QA, sem nomeação operacional',
|
||
}))
|
||
await scalar(
|
||
page,
|
||
key,
|
||
'canonical.milestones.' + i + '.' + key,
|
||
value,
|
||
);
|
||
await page
|
||
.getByLabel('Motivo da alteração', { exact: true })
|
||
.fill(
|
||
'Completar marcos sintéticos QA conforme o contrato atual; datas de ensaio, sem compromisso real.',
|
||
);
|
||
const pending = page.waitForResponse(
|
||
(r) =>
|
||
r.request().method() === 'PUT' &&
|
||
new URL(r.url()).pathname.endsWith('/draft'),
|
||
);
|
||
await page
|
||
.getByRole('button', { name: 'Guardar rascunho', exact: true })
|
||
.click();
|
||
assert.equal((await pending).status(), 200);
|
||
await page.waitForLoadState('networkidle');
|
||
w.current_milestones_saved = true;
|
||
q.save();
|
||
}
|
||
if (!w.current_publication_fields_saved) {
|
||
q.phase(
|
||
'Current contract: declared QA illustration and editorial review date',
|
||
);
|
||
if (!w.image) {
|
||
await open('/minhas-oportunidades/' + w.id + '/resources');
|
||
await page
|
||
.getByRole('button', { name: 'Imagens', exact: true })
|
||
.click();
|
||
await page
|
||
.getByLabel('Título', { exact: true })
|
||
.fill('Ilustração sintética QA do workflow');
|
||
await page
|
||
.getByLabel('Texto alternativo', { exact: true })
|
||
.fill(
|
||
'Retângulo verde com identificação de ensaio sintético QA',
|
||
);
|
||
await page
|
||
.getByLabel('Autor / fonte', { exact: true })
|
||
.fill('Asset sintético gerado localmente para ensaio QA');
|
||
await page
|
||
.getByLabel('Fonte / referência', { exact: true })
|
||
.fill(
|
||
'portal-consumer-image-fixture-qa.php — não retrata investimento real',
|
||
);
|
||
await page
|
||
.getByLabel('Tipo de imagem', { exact: true })
|
||
.selectOption('cover');
|
||
await page
|
||
.getByLabel('Direitos de utilização', { exact: true })
|
||
.selectOption('owned');
|
||
await page
|
||
.getByLabel('Âmbito de consulta', { exact: true })
|
||
.selectOption('internal');
|
||
await page
|
||
.getByLabel('Motivo (20–1000 caracteres)', { exact: true })
|
||
.fill(
|
||
'Imagem sintética própria de ensaio QA, não corresponde a um investimento real.',
|
||
);
|
||
await page
|
||
.locator('input[type=file]')
|
||
.setInputFiles(
|
||
path.join(DIR, 'consumer-workflow-synthetic.png'),
|
||
);
|
||
const pending = page.waitForResponse(
|
||
(r) =>
|
||
new URL(r.url()).pathname.endsWith(
|
||
'/resources/images',
|
||
) && r.request().method() === 'POST',
|
||
);
|
||
await page
|
||
.getByRole('button', {
|
||
name: 'Guardar recurso',
|
||
exact: true,
|
||
})
|
||
.click();
|
||
const r = await pending;
|
||
assert.equal(r.status(), 201);
|
||
w.image = (await r.json()).id;
|
||
q.save();
|
||
}
|
||
await open('/minhas-oportunidades/' + w.id + '/edit');
|
||
const field = page.locator(
|
||
'fieldset:has(> legend:text-is("Imagens guardadas"))',
|
||
);
|
||
const index = props.canonicalOptions.resources.images.findIndex(
|
||
(r) => r.id === w.image,
|
||
);
|
||
assert(index >= 0);
|
||
await field.locator('input[type=checkbox]').nth(index).check();
|
||
await scalar(
|
||
page,
|
||
'review_due_date',
|
||
'canonical.review_due_date',
|
||
'2026-11-01',
|
||
);
|
||
await page
|
||
.getByLabel('Motivo da alteração', { exact: true })
|
||
.fill(
|
||
'Identificação da ilustração QA e data editorial de ensaio conforme contrato atual.',
|
||
);
|
||
const pending = page.waitForResponse(
|
||
(r) =>
|
||
r.request().method() === 'PUT' &&
|
||
new URL(r.url()).pathname.endsWith('/draft'),
|
||
);
|
||
await page
|
||
.getByRole('button', { name: 'Guardar rascunho', exact: true })
|
||
.click();
|
||
assert.equal((await pending).status(), 200);
|
||
await page.waitForLoadState('networkidle');
|
||
w.current_publication_fields_saved = true;
|
||
q.save();
|
||
}
|
||
if (!w.current_finance_environment_saved) {
|
||
q.phase(
|
||
'UI completes zero secured funding and explicit pending environmental classification',
|
||
);
|
||
await open('/minhas-oportunidades/' + w.id + '/edit');
|
||
const funding = page
|
||
.locator(
|
||
'fieldset:has(> legend:text-is(' +
|
||
JSON.stringify(label('secured_financing')) +
|
||
'))',
|
||
)
|
||
.first();
|
||
await funding.locator(':scope > button').click();
|
||
await funding
|
||
.getByRole('button', { name: 'Retirar linha 1', exact: true })
|
||
.click();
|
||
for (const [key, value] of Object.entries({
|
||
assessment_status: 'Avaliação pendente — ensaio sintético QA',
|
||
environmental_category:
|
||
'Classificação por determinar — ensaio sintético QA',
|
||
}))
|
||
await scalar(
|
||
page,
|
||
key,
|
||
'canonical.environmental_social.' + key,
|
||
value,
|
||
);
|
||
await page
|
||
.getByLabel('Motivo da alteração', { exact: true })
|
||
.fill(
|
||
'Declarar cenário sintético QA sem financiamento assegurado e classificação ambiental pendente.',
|
||
);
|
||
const pending = page.waitForResponse(
|
||
(r) =>
|
||
r.request().method() === 'PUT' &&
|
||
new URL(r.url()).pathname.endsWith('/draft'),
|
||
);
|
||
await page
|
||
.getByRole('button', { name: 'Guardar rascunho', exact: true })
|
||
.click();
|
||
assert.equal((await pending).status(), 200);
|
||
await page.waitForLoadState('networkidle');
|
||
w.current_finance_environment_saved = true;
|
||
q.save();
|
||
}
|
||
if (!w.cover_derivatives_prepared) {
|
||
q.phase(
|
||
'UI cover metadata and real WebP preparation before approval',
|
||
);
|
||
await open('/minhas-oportunidades/' + w.id + '/resources');
|
||
await page
|
||
.getByRole('button', { name: 'Imagens', exact: true })
|
||
.click();
|
||
const image = page.locator('article').filter({
|
||
has: page.getByText(/^Ilustração sintética QA do workflow/),
|
||
});
|
||
await image
|
||
.getByRole('button', {
|
||
name: 'Ver / editar / restaurar',
|
||
exact: true,
|
||
})
|
||
.click();
|
||
await page
|
||
.getByLabel('Legenda', { exact: true })
|
||
.fill(
|
||
'Ilustração própria do ensaio QA, sem representação de investimento real.',
|
||
);
|
||
await page
|
||
.getByLabel('Selecionar capa de preparação', { exact: true })
|
||
.check();
|
||
await page
|
||
.getByLabel('Motivo (20–1000 caracteres)', { exact: true })
|
||
.fill(
|
||
'Identificar explicitamente a capa sintética QA e preparar derivadas verificadas.',
|
||
);
|
||
const saved = page.waitForResponse(
|
||
(r) =>
|
||
r.request().method() === 'POST' &&
|
||
new URL(r.url()).pathname.endsWith(
|
||
'/resources/images/' + w.image,
|
||
),
|
||
);
|
||
await page
|
||
.getByRole('button', { name: 'Guardar recurso', exact: true })
|
||
.click();
|
||
assert.equal((await saved).status(), 200);
|
||
await page.waitForLoadState('networkidle');
|
||
await open('/minhas-oportunidades/' + w.id + '/resources');
|
||
await page
|
||
.getByRole('button', { name: 'Imagens', exact: true })
|
||
.click();
|
||
await page
|
||
.locator('article')
|
||
.filter({
|
||
has: page.getByText(/^Ilustração sintética QA do workflow/),
|
||
})
|
||
.getByRole('button', {
|
||
name: 'Ver / editar / restaurar',
|
||
exact: true,
|
||
})
|
||
.click();
|
||
await page
|
||
.getByLabel('Motivo (20–1000 caracteres)', { exact: true })
|
||
.fill(
|
||
'Preparar WebP e miniaturas da imagem sintética QA pelo serviço existente.',
|
||
);
|
||
const prepared = page.waitForResponse(
|
||
(r) =>
|
||
r.request().method() === 'POST' &&
|
||
new URL(r.url()).pathname.includes(
|
||
'/resources/images/' + w.image + '/',
|
||
),
|
||
);
|
||
await page
|
||
.getByRole('button', {
|
||
name: 'Preparar WebP e miniaturas',
|
||
exact: true,
|
||
})
|
||
.click();
|
||
assert.equal((await prepared).status(), 200);
|
||
await page.waitForLoadState('networkidle');
|
||
w.cover_derivatives_prepared = true;
|
||
q.save();
|
||
}
|
||
await open('/minhas-oportunidades/' + w.id + '/view');
|
||
if (props.workflowState === 'draft') await transition('submit');
|
||
await actor('validator');
|
||
await open('/minhas-oportunidades/' + w.id + '/view');
|
||
if (!w.correction_requested) {
|
||
await transition('request_correction', {
|
||
correction: 'canonical.demand_statement',
|
||
});
|
||
w.correction_requested = true;
|
||
q.save();
|
||
}
|
||
await actor('promoter');
|
||
await open('/minhas-oportunidades/' + w.id + '/edit');
|
||
if (props.workflowState === 'needs_information') {
|
||
await page
|
||
.locator('[id="canonical.demand_statement"]')
|
||
.fill(
|
||
props.values.canonical.demand_statement +
|
||
' Correção visual QA: informação solicitada acrescentada.',
|
||
);
|
||
await page
|
||
.getByLabel('Motivo da alteração', { exact: true })
|
||
.fill(
|
||
'Resposta sintética QA à correção solicitada pelo Validador independente.',
|
||
);
|
||
const response = page.waitForResponse(
|
||
(r) =>
|
||
r.request().method() === 'PUT' &&
|
||
new URL(r.url()).pathname.endsWith('/draft'),
|
||
);
|
||
await page
|
||
.getByRole('button', { name: 'Guardar rascunho', exact: true })
|
||
.click();
|
||
assert.equal((await response).status(), 200);
|
||
await page.waitForLoadState('networkidle');
|
||
w.steps.push({ actor: 21, action: 'correct_saved' });
|
||
q.save();
|
||
await transition('submit');
|
||
}
|
||
await actor('validator');
|
||
await open('/minhas-oportunidades/' + w.id + '/view');
|
||
if (props.workflowState === 'submitted')
|
||
await transition('start_review');
|
||
if (props.workflowState === 'in_review') {
|
||
for (const gate of props.canonicalOptions.gates) {
|
||
if (
|
||
!w.steps.some(
|
||
(s) => s.action === 'review_gate' && s.gate === gate,
|
||
)
|
||
)
|
||
await transition('review_gate', { gate });
|
||
}
|
||
await transition('approve');
|
||
}
|
||
await actor('publisher');
|
||
await open('/minhas-oportunidades/' + w.id + '/view');
|
||
if (props.workflowState === 'approved') await transition('publish');
|
||
assert.equal(props.workflowState, 'published');
|
||
assert.equal(props.publicationState.public_catalog, true);
|
||
w.publication = {
|
||
state: props.workflowState,
|
||
scope: props.publicationState.scope,
|
||
label: props.publicationState.label,
|
||
public_catalog: true,
|
||
};
|
||
q.proof.checks.independent_visual_cycle = true;
|
||
q.proof.checks.publication_scope_explicit = true;
|
||
await actor('promoter');
|
||
await open('/minhas-oportunidades/' + w.id + '/view');
|
||
assert.equal(props.workflowState, 'published');
|
||
assert(props.profileContext.approval);
|
||
q.proof.checks.final_state_recovered_by_promoter = true;
|
||
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,
|
||
)
|
||
) {
|
||
q.proof.failure_state = {
|
||
route: new URL(page.url()).pathname,
|
||
labels: (await page.locator('label').allTextContents()).map(
|
||
(s) => s.slice(0, 140),
|
||
),
|
||
alerts: await page.getByRole('alert').allTextContents(),
|
||
buttons: await page.getByRole('button').allTextContents(),
|
||
};
|
||
await q.shot(page, 'failure', false).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,
|
||
checks: q.proof.checks,
|
||
receipt: q.receipt,
|
||
}),
|
||
);
|
||
if (q.proof.result === 'failed') process.exitCode = 1;
|
||
}
|
||
})();
|