generated from jric11/baseProject
55 lines
9.3 KiB
JavaScript
55 lines
9.3 KiB
JavaScript
const {chromium}=require('playwright');
|
|
const assert=require('node:assert/strict');
|
|
const path=require('node:path');
|
|
const fs=require('node:fs/promises');
|
|
(async()=>{
|
|
const browser=await chromium.launch({executablePath:process.env.HCR_TEST_CHROME||'C:/Program Files/Google/Chrome/Application/chrome.exe',headless:true});
|
|
const page=await browser.newPage({viewport:{width:1440,height:1000}});
|
|
const errors=[],checks=[];page.on('pageerror',error=>errors.push(error.message));
|
|
await page.exposeFunction('recordCspViolation',message=>errors.push(message));await page.addInitScript(()=>document.addEventListener('securitypolicyviolation',event=>window.recordCspViolation('CSP: '+event.violatedDirective+' '+event.blockedURI)));
|
|
const pass=message=>{checks.push(message);console.log('PASS: '+message);};
|
|
const submit=async data=>{await Promise.all([page.waitForNavigation({waitUntil:'load'}),page.evaluate(data=>formSubmit(data),data)]);await page.waitForFunction(()=>typeof formSubmit==='function');await page.waitForLoadState('networkidle');};
|
|
try{
|
|
await page.goto(process.env.HCR_TEST_URL+'/main.php');
|
|
await page.locator('#user_email').fill('admin@example.test');await page.locator('#user_password').fill('Admin test password 123!');await Promise.all([page.waitForNavigation({waitUntil:'load'}),page.locator('#login button[type="submit"]').click()]);await page.waitForFunction(()=>typeof formSubmit==='function');
|
|
pass('Browser login loads the application shell without script errors');
|
|
const safe=await page.evaluate(()=>{
|
|
const original=HTMLFormElement.prototype.submit;HTMLFormElement.prototype.submit=function(){};
|
|
try{const value='"><img id="injected-hidden-value" src=x>';formSubmit({action:'Users.setupUsers',find_user:value});return !document.querySelector('#injected-hidden-value')&&document.querySelector('#formSubmit input[name="find_user"]').value===value;}finally{HTMLFormElement.prototype.submit=original;}
|
|
});assert(safe);pass('Hidden form values remain text rather than injected HTML');
|
|
await submit({action:'Users.setupUsers'});await page.locator('.setupUsers').waitFor();
|
|
await page.locator('.btnAdd').click();await page.locator('#addUser').waitFor();
|
|
await page.locator('#user_name').fill('browser_tester');await page.locator('#user_first_name').fill('Browser');await page.locator('#user_last_name').fill('Tester');await page.locator('#user_email').fill('browser@example.test');await page.locator('#user_role').selectOption('User');
|
|
await page.locator('.btnSave').click();await page.locator('.setupUsers').waitFor();
|
|
await page.getByText('Browser',{exact:true}).waitFor();pass('Administrative account form saves through asynchronous validation');
|
|
assert((await page.locator('.toast-body').innerText()).includes('invitation'));pass('Failed invitation is visible after returning to the user list');
|
|
const row=page.locator('tr.user-row').filter({hasText:'browser_tester'});await row.click();await page.locator('.userSummary').waitFor();pass('Clicking a user opens the repaired summary');
|
|
await page.locator('.summary-edit').click();await page.locator('#editUser').waitFor();
|
|
const failUsage=async route=>{
|
|
const data=route.request().postData()||'';
|
|
if(data.includes('Users.userActive'))await route.fulfill({status:500,contentType:'application/json',body:JSON.stringify({ok:false,error:'Deliberate usage-check failure'})});else await route.continue();
|
|
};
|
|
await page.route('**/*',failUsage);await page.locator('.btnDelete').click();await page.getByText('Deliberate usage-check failure',{exact:true}).waitFor();assert(!await page.locator('#confirm-dialog').isVisible());pass('A failed deletion check shows an error and does not open confirmation');await page.unroute('**/*',failUsage);
|
|
await page.locator('.btnDelete').click();await page.locator('#confirm-dialog').waitFor({state:'visible'});await page.locator('#confirm-dialog .btnConfirmDialogConfirm').click();await page.locator('.setupUsers').waitFor();assert(await page.locator('tr.user-row').filter({hasText:'browser_tester'}).count()===0);pass('Deleting an unused account succeeds through the browser');
|
|
await submit({action:'Dropdowns.setupDropdowntypes'});await page.locator('.setupDropdowntypes').waitFor();await page.locator('.btnAdd').click();await page.locator('#addDropdowntype').waitFor();await page.locator('#dropdowntype_name').fill('browser_type');await page.locator('#dropdowntype_title').fill('Browser type');await page.locator('.btnSave').click();await page.locator('.setupDropdowntypes').waitFor();pass('Dropdown type form works with restricted usage metadata');
|
|
await submit({action:'Alerts.addAlert'});await page.locator('.ck-editor').waitFor();pass('Alert editor initializes with updated jQuery and the security policy');
|
|
await submit({action:'Popovers.addPopover'});await page.locator('.ck-editor').waitFor();pass('Popover editor initializes with updated jQuery and the security policy');
|
|
await page.goto(process.env.HCR_TEST_URL+'/main.php?Xaction=Roster.workspace');
|
|
let form=page.locator('form').filter({has:page.getByRole('button',{name:'Create club',exact:true})});await form.getByLabel('Club name',{exact:true}).fill('Browser Club');await form.getByRole('button',{name:'Create club',exact:true}).click();await page.getByRole('heading',{name:'Browser Club',exact:true}).waitFor();pass('Club creation works in the browser');
|
|
form=page.locator('form').filter({has:page.getByRole('button',{name:'Add member',exact:true})});await form.getByLabel('Username or email').fill('testadmin');await form.getByRole('button',{name:'Add member',exact:true}).click();await page.getByRole('cell',{name:'Test Administrator',exact:true}).waitFor();pass('Membership assignment works in the browser');
|
|
form=page.locator('form').filter({has:page.getByRole('button',{name:'Add season',exact:true})});await form.getByLabel('Season name').fill('Browser Season');await form.getByLabel('Starts on').fill('2026-01-01');await form.getByLabel('Ends on').fill('2026-12-31');await form.getByRole('button',{name:'Add season',exact:true}).click();await page.getByText('Browser Season (2026-01-01 to 2026-12-31)',{exact:true}).waitFor();pass('Season creation works in the browser');
|
|
form=page.locator('form').filter({has:page.getByRole('button',{name:'Save dues',exact:true})});await form.getByLabel('Dues (USD)').fill('10.00');await form.getByRole('button',{name:'Save dues',exact:true}).click();await page.getByRole('button',{name:'Record payment',exact:true}).waitFor();pass('Dues entry works in the browser');
|
|
form=page.locator('form').filter({has:page.getByRole('button',{name:'Record payment',exact:true})});await form.getByLabel('Payment (USD)').fill('5.00');await form.getByRole('button',{name:'Record payment',exact:true}).click();await page.getByRole('cell',{name:'$5.00',exact:true}).first().waitFor();pass('Recording a payment updates the displayed balance');
|
|
await page.getByText('Correct dues',{exact:true}).click();
|
|
form=page.locator('form').filter({has:page.getByRole('button',{name:'Save correction',exact:true})});await form.getByLabel('Corrected total (USD)').fill('8.00');await form.getByLabel('Reason',{exact:true}).fill('Correct season rate');await form.getByRole('button',{name:'Save correction',exact:true}).click();await page.getByRole('cell',{name:'$8.00',exact:true}).waitFor();pass('Dues correction updates the balance and preserves history in the browser');
|
|
await page.getByText('Reverse incorrect payment',{exact:true}).click();
|
|
form=page.locator('form').filter({has:page.getByRole('button',{name:'Confirm reversal',exact:true})});await form.getByLabel('Reversal reason').fill('Wrong receipt');await form.getByRole('button',{name:'Confirm reversal',exact:true}).click();await page.getByText('Reversed: Wrong receipt',{exact:false}).waitFor();pass('Payment reversal is recorded visibly without deleting the original payment');
|
|
const ledgerDownload=page.waitForEvent('download');await page.getByRole('button',{name:'Export financial history (CSV)',exact:true}).click();assert.equal((await ledgerDownload).suggestedFilename(),'club-financial-history.csv');pass('Financial history can be downloaded from the browser');
|
|
await page.screenshot({path:path.join(__dirname,'.local','roster.png'),fullPage:true});
|
|
await submit({action:'Reports.selectReport'});await page.getByRole('heading',{name:'Roster reports'}).waitFor();const downloadEvent=page.waitForEvent('download');await page.getByRole('button',{name:'Download CSV'}).click();const download=await downloadEvent;assert.equal(download.suggestedFilename(),'club-roster.csv');pass('Report catalog downloads the roster CSV in the browser');
|
|
await page.goto(process.env.HCR_TEST_URL+'/main.php?Xaction=verifyemail');await page.getByRole('heading',{name:'Verify your email'}).waitFor();await page.getByLabel('Email',{exact:true}).fill('unknown@example.test');await page.getByRole('button',{name:'Send verification link'}).click();await page.getByText('If an active account needs verification',{exact:false}).waitFor();pass('Verification request page gives a neutral response in the browser');
|
|
assert.deepEqual(errors,[]);pass('No unexpected browser JavaScript errors occurred');console.log(checks.length+' browser checks passed.');
|
|
}catch(error){await page.screenshot({path:path.join(__dirname,'.local','browser-failure.png'),fullPage:true});console.error('PAGE ERRORS:',errors);throw error;}
|
|
finally{await browser.close();}
|
|
})().catch(error=>{console.error(error);process.exitCode=1;});
|