Enhance CI configuration and documentation; add new action palette adapter
Some checks failed
CI / validate (push) Has been cancelled
Some checks failed
CI / validate (push) Has been cancelled
This commit is contained in:
71
test/unit/qkforce-generated.test.mjs
Normal file
71
test/unit/qkforce-generated.test.mjs
Normal file
@@ -0,0 +1,71 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
import { loadGeneratedWorkspace, loadGeneratedSelectors } from '../support/qkforce-generated.mjs';
|
||||
import { createQkfActions } from '../support/qkforce-actions.mjs';
|
||||
|
||||
function fixture(t, features=['test/qkforce/features/project.feature']) {
|
||||
const root=fs.mkdtempSync(path.join(os.tmpdir(),'qkf-generated-'));
|
||||
t.after(()=>fs.rmSync(root,{recursive:true,force:true}));
|
||||
fs.mkdirSync(path.join(root,'test/qkforce/features'),{recursive:true});
|
||||
fs.writeFileSync(path.join(root,'test/qkforce/features/project.feature'),'Feature: project');
|
||||
fs.writeFileSync(path.join(root,'test/qkforce/steps.mjs'),'');
|
||||
fs.writeFileSync(path.join(root,'test/qkforce/selectors.json'),'{}');
|
||||
const manifest={schemaVersion:1,features,steps:'test/qkforce/steps.mjs',selectors:'test/qkforce/selectors.json'};
|
||||
fs.writeFileSync(path.join(root,'test/qkforce/manifest.json'),JSON.stringify(manifest));
|
||||
return root;
|
||||
}
|
||||
|
||||
test('runs exactly generated cases and never template or stale feature globs', t=>{
|
||||
const root=fixture(t);
|
||||
fs.writeFileSync(path.join(root,'test/qkforce/features/stale.feature'),'Feature: stale');
|
||||
assert.deepEqual(loadGeneratedWorkspace(root),{specs:['./test/qkforce/features/project.feature'],imports:['./test/qkforce/steps.mjs']});
|
||||
assert.deepEqual(loadGeneratedSelectors(root),{});
|
||||
});
|
||||
test('an empty generated selection fails instead of running the template sample',t=>{
|
||||
assert.throws(()=>loadGeneratedWorkspace(fixture(t,[])),/at least one/);
|
||||
});
|
||||
test('an API package without generated selection cannot fall back to samples',t=>{
|
||||
const root=fixture(t);fs.unlinkSync(path.join(root,'test/qkforce/manifest.json'));
|
||||
assert.equal(loadGeneratedWorkspace(root),null);
|
||||
fs.writeFileSync(path.join(root,'qkforce-package-manifest.json'),'{}');
|
||||
assert.throws(()=>loadGeneratedWorkspace(root),/regenerate/);
|
||||
});
|
||||
test('unsafe, missing, duplicate and symlinked feature inputs are rejected',t=>{
|
||||
for(const features of [['test/qkforce/features/../../secret.feature'],['test/qkforce/features/missing.feature'],['test/qkforce/features/project.feature','test/qkforce/features/project.feature']]) {
|
||||
assert.throws(()=>loadGeneratedWorkspace(fixture(t,features)));
|
||||
}
|
||||
const root=fixture(t);
|
||||
const feature=path.join(root,'test/qkforce/features/project.feature');
|
||||
fs.unlinkSync(feature);fs.symlinkSync(path.join(root,'test/qkforce/steps.mjs'),feature);
|
||||
assert.throws(()=>loadGeneratedWorkspace(root),/symlinks/);
|
||||
});
|
||||
test('legacy unawaited actions execute sequentially and finish before flush returns',async()=>{
|
||||
const events=[];
|
||||
const browser={url:async url=>{await Promise.resolve();events.push(url);},$:async selector=>({click:async()=>events.push(selector)})};
|
||||
const qkf=createQkfActions(browser);
|
||||
qkf.goto('/login');qkf.click('#submit');
|
||||
await qkf.flush();assert.deepEqual(events,['/login','#submit']);
|
||||
});
|
||||
test('queued action failures fail the step and reset for the next scenario',async()=>{
|
||||
let fail=true;
|
||||
const qkf=createQkfActions({url:async()=>{if(fail)throw new Error('navigation failed');}});
|
||||
qkf.goto('/login');await assert.rejects(qkf.flush(),/navigation failed/);
|
||||
fail=false;qkf.goto('/ok');await qkf.flush();
|
||||
});
|
||||
test('all palette element actions map to real WDIO methods',async()=>{
|
||||
const calls=[];
|
||||
const element=new Proxy({}, {get:(_,method)=>method === 'then' ? undefined : (...args)=>{calls.push([method,...args]);return true;}});
|
||||
const qkf=createQkfActions({$:()=>element});
|
||||
for(const method of ['click','doubleClick','rightClick','enterValue','addValue','clearValue','isVisible','exists','isEnabled','isClickable','isSelected','waitForVisible','waitForExistence','waitForClickable','waitForEnabled']) qkf[method]('#x','value');
|
||||
await qkf.flush();
|
||||
assert.equal(calls.length,15);assert.deepEqual(calls[2],['click',{button:'right'}]);assert.deepEqual(calls[3],['setValue','value']);
|
||||
});
|
||||
test('screenshots stay inside the results directory',async t=>{
|
||||
const root=fixture(t);let saved;
|
||||
const qkf=createQkfActions({saveScreenshot:async file=>{saved=file;}},root);
|
||||
qkf.takeScreenshot('../../outside');await qkf.flush();
|
||||
assert.equal(path.dirname(saved),path.join(root,'qreport-results/screenshots'));
|
||||
});
|
||||
24
test/unit/run-profile.test.mjs
Normal file
24
test/unit/run-profile.test.mjs
Normal file
@@ -0,0 +1,24 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { executeAndReport } from '../../scripts/run-profile.mjs';
|
||||
|
||||
test('builds the report after both passing and failing runs, preserving runner status', async () => {
|
||||
for (const code of [0, 1, 7]) {
|
||||
const events = [];
|
||||
const result = await executeAndReport(async () => { events.push('runner'); return code; }, async () => { events.push('report'); });
|
||||
assert.equal(result, code);
|
||||
assert.deepEqual(events, ['runner', 'report']);
|
||||
}
|
||||
});
|
||||
|
||||
test('attempts reporting after runner startup failure', async () => {
|
||||
let reported = false;
|
||||
assert.equal(await executeAndReport(async () => { throw new Error('spawn failed'); }, () => { reported = true; }), 1);
|
||||
assert.ok(reported);
|
||||
});
|
||||
|
||||
test('a report failure cannot convert a failed runner to success or hide its exit code', async () => {
|
||||
for (const code of [0, 7]) {
|
||||
assert.equal(await executeAndReport(async () => code, () => { throw new Error('unwritable report'); }), code || 1);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user