166 lines
12 KiB
JavaScript
166 lines
12 KiB
JavaScript
|
|
/* Isolated mobile/tablet UI tests: all requests are mocked, no Hub data is changed.
|
||
|
|
* BMC_PLAYWRIGHT_MODULE=/path/to/playwright node tests/browser/remote_control.cjs
|
||
|
|
* BMC_REMOTE_BROWSER=webkit tests the Safari/WebKit engine.
|
||
|
|
*/
|
||
|
|
const assert = require('node:assert/strict');
|
||
|
|
const fs = require('node:fs');
|
||
|
|
const pw = require(process.env.BMC_PLAYWRIGHT_MODULE || 'playwright');
|
||
|
|
const engine = process.env.BMC_REMOTE_BROWSER || 'chromium';
|
||
|
|
|
||
|
|
(async () => {
|
||
|
|
const browser = await pw[engine].launch(engine === 'chromium'
|
||
|
|
? {headless:true, executablePath:process.env.BMC_CHROME_EXECUTABLE || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'}
|
||
|
|
: {headless:true});
|
||
|
|
try {
|
||
|
|
for (const viewport of [{width:390,height:844}, {width:820,height:1180}]) {
|
||
|
|
const context = await browser.newContext({viewport});
|
||
|
|
const page = await context.newPage();
|
||
|
|
const errors = [], writes = [], completed = new Map();
|
||
|
|
let disconnected = false, dropReply = false;
|
||
|
|
const cases = [11,12].map(id => ({id,titel:'Testsag '+id,status:'Ny',deadline:null,deadline_state:'none'}));
|
||
|
|
let state = {user_id:1,user_name:'Testbruger',version:0,timer_version:'a'.repeat(64),active_case:cases[0],desktop_connected:false,
|
||
|
|
server_time:new Date().toISOString(),
|
||
|
|
timer:{active:{active:false,elapsed:0},paused:[]},today_minutes:0,messages:{count:1,list:[{id:1,from:'Kollega',text:'Testbesked',is_unread:true}]}};
|
||
|
|
page.on('pageerror', e => errors.push(e.message));
|
||
|
|
await page.route('**/*', async route => {
|
||
|
|
const req = route.request(), path = new URL(req.url()).pathname;
|
||
|
|
if (path === '/remote') return route.fulfill({contentType:'text/html',body:fs.readFileSync('app/modules/remote/mobile.html','utf8')});
|
||
|
|
if (path.startsWith('/static/')) return route.fulfill({contentType:path.endsWith('.css')?'text/css':'application/javascript',body:fs.readFileSync('.'+path,'utf8')});
|
||
|
|
if (disconnected) return route.abort('internetdisconnected');
|
||
|
|
let result = {};
|
||
|
|
if (path.endsWith('/bootstrap')) result = {csrf:'test-csrf',status:'connect'};
|
||
|
|
if (path.endsWith('/connect')) {writes.push(req.postDataJSON());result={ok:true};}
|
||
|
|
if (path.endsWith('/state')) result = state;
|
||
|
|
if (path.endsWith('/telephony')) result = {allowed:true,incoming:[{call_id:'incoming-1',contact_name:'Indgående testkontakt',caller_number:'12345678',company_name:'Testfirma',ringing_phones:['Support']}]};
|
||
|
|
if (path.endsWith('/contacts')) result = {items:[{id:1,first_name:'Test',last_name:'Kontakt',mobile:'87654321',company_names:['Testfirma']}],has_more:false};
|
||
|
|
if (path.endsWith('/call')) {writes.push(req.postDataJSON());result={status:'ok',message:'Opkald startet på din VoIP-telefon'};}
|
||
|
|
if (path.endsWith('/cases')) result = {items:cases,has_more:false};
|
||
|
|
if (path.includes('/command-result/')) result = {completed:completed.has(path.split('/').pop())};
|
||
|
|
if (path.endsWith('/command')) {
|
||
|
|
const data = req.postDataJSON(); writes.push(data);
|
||
|
|
if (!completed.has(data.request_id)) {
|
||
|
|
state.version++;
|
||
|
|
if (data.action === 'start') state.timer.active={active:true,sag_id:data.case_id,sag_navn:'Testsag '+data.case_id,time_entry_id:1,elapsed:10};
|
||
|
|
if (data.action === 'select') {state.active_case=cases.find(c=>c.id===data.case_id);if(data.switch_timer)state.timer.active.sag_id=data.case_id;}
|
||
|
|
if (data.action === 'pause') {state.timer.paused=[{sag_navn:'Testsag',time_entry_id:1,elapsed_hhmmss:'00:00:10'}];state.timer.active.active=false;}
|
||
|
|
if (data.action === 'resume') {state.timer.active.active=true;state.timer.paused=[];}
|
||
|
|
if (data.action === 'stop') state.timer.active.active=false;
|
||
|
|
if (data.action === 'manual') state.today_minutes+=data.minutes;
|
||
|
|
completed.set(data.request_id,{ok:true});
|
||
|
|
}
|
||
|
|
result=completed.get(data.request_id);
|
||
|
|
if(dropReply){dropReply=false;return route.abort('internetdisconnected');}
|
||
|
|
}
|
||
|
|
return route.fulfill({contentType:'application/json',body:JSON.stringify(result)});
|
||
|
|
});
|
||
|
|
await page.goto('http://remote.test/remote');
|
||
|
|
await page.locator('#code').fill('000000000042');
|
||
|
|
await page.locator('#pin').fill('1234');
|
||
|
|
await page.getByRole('button',{name:'Forbind',exact:true}).click();
|
||
|
|
await page.locator('#controls').waitFor({state:'visible'});
|
||
|
|
assert.equal(writes[0].code,'000000000042');
|
||
|
|
assert.equal(await page.locator('#deadline').textContent(),'Ingen deadline');
|
||
|
|
assert.equal(await page.locator('#messages').textContent().then(s=>s.includes('Testbesked')),true);
|
||
|
|
await page.getByRole('button',{name:'Start timer',exact:true}).evaluate(b=>{b.click();b.click();});
|
||
|
|
await page.waitForFunction(()=>document.querySelector('[data-action="pause"]').disabled===false);
|
||
|
|
assert.equal(writes.filter(x=>x.action==='start').length,1);
|
||
|
|
await page.getByText('Indgående testkontakt',{exact:true}).waitFor();
|
||
|
|
await page.locator('#contact-search').fill('Kontakt');
|
||
|
|
await page.getByRole('button',{name:'Søg kontakter',exact:true}).click();
|
||
|
|
await page.getByRole('button',{name:'Ring mobil · 87654321',exact:true}).evaluate(b=>{b.click();b.click();});
|
||
|
|
await page.getByText('Opkald startet på din VoIP-telefon',{exact:true}).waitFor();
|
||
|
|
assert.equal(writes.filter(x=>x.contact_id===1).length,1);
|
||
|
|
|
||
|
|
await page.locator('.case').filter({hasText:'#12'}).click();
|
||
|
|
await page.getByRole('button',{name:'Kun skift sag',exact:true}).click();
|
||
|
|
await page.waitForFunction(()=>document.querySelector('#active-case').textContent.startsWith('#12'));
|
||
|
|
assert.equal(state.timer.active.sag_id,11);
|
||
|
|
await page.getByRole('button',{name:'Pause',exact:true}).click();
|
||
|
|
await page.getByRole('button',{name:'Genoptag',exact:true}).waitFor();
|
||
|
|
await page.getByRole('button',{name:'Genoptag',exact:true}).click();
|
||
|
|
await page.waitForFunction(()=>document.querySelector('[data-action="pause"]').disabled===false);
|
||
|
|
await page.locator('#description').fill('Testarbejde');
|
||
|
|
await page.getByRole('button',{name:'Stop og gem tid',exact:true}).click();
|
||
|
|
await page.waitForFunction(()=>document.querySelector('[data-action="start"]').disabled===false);
|
||
|
|
await page.locator('#description').fill('Manuel test');await page.locator('#minutes').fill('17');
|
||
|
|
dropReply=true;
|
||
|
|
await page.getByRole('button',{name:'Gem tid med beskrivelsen ovenfor',exact:true}).click();
|
||
|
|
await page.locator('#uncertain').waitFor({state:'visible'});
|
||
|
|
await page.waitForFunction(()=>!document.querySelector('#check-command').disabled);
|
||
|
|
await page.getByRole('button',{name:'Kontrollér resultat',exact:true}).click();
|
||
|
|
await page.locator('#uncertain').waitFor({state:'hidden'});
|
||
|
|
assert.equal(state.today_minutes,17);
|
||
|
|
assert.equal(writes.filter(x=>x.action==='manual').length,1);
|
||
|
|
disconnected=true;
|
||
|
|
await page.waitForFunction(()=>document.querySelector('[data-action="start"]').disabled===true);
|
||
|
|
const count=writes.length;
|
||
|
|
await page.getByRole('button',{name:'Start timer',exact:true}).evaluate(b=>b.click());
|
||
|
|
assert.equal(writes.length,count);
|
||
|
|
disconnected=false;
|
||
|
|
await page.waitForFunction(()=>document.querySelector('[data-action="start"]').disabled===false);
|
||
|
|
assert.equal(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth),true);
|
||
|
|
assert.deepEqual(errors,[]);
|
||
|
|
await page.screenshot({path:`/tmp/bmc-remote-${engine}-${viewport.width}.png`,fullPage:true});
|
||
|
|
await context.close();
|
||
|
|
console.log(`${engine} ${viewport.width}px: login, timer, sagsvalg, manuel tid, tabt svar og forbindelse OK`);
|
||
|
|
}
|
||
|
|
// Normal Hub administration uses separate authenticated routes and clears codes.
|
||
|
|
const context = await browser.newContext({viewport:{width:820,height:1180}});
|
||
|
|
const page = await context.newPage();
|
||
|
|
const errors = [], adminWrites = [];
|
||
|
|
const admin = {pin_set:false,remotes:[],devices:[]};
|
||
|
|
let follower = null, seq = 0;
|
||
|
|
await context.route('**/*', async route => {
|
||
|
|
const req=route.request(), path=new URL(req.url()).pathname;
|
||
|
|
if(path==='/remote/manage')return route.fulfill({contentType:'text/html',body:fs.readFileSync('app/modules/remote/manage.html','utf8')});
|
||
|
|
if(path==='/' || path.startsWith('/sag/'))return route.fulfill({contentType:'text/html',body:'<!doctype html><meta charset="utf-8"><title>Test Hub</title><input id="edit"><script src="/static/js/remote-hub.js" defer></script>'});
|
||
|
|
if(path.startsWith('/static/'))return route.fulfill({contentType:path.endsWith('.css')?'text/css':'application/javascript',body:fs.readFileSync('.'+path,'utf8')});
|
||
|
|
let result={};
|
||
|
|
if(path.endsWith('/bootstrap'))result={csrf:'test-csrf',status:'connect'};
|
||
|
|
if(path.endsWith('/admin'))result=admin;
|
||
|
|
if(req.method()!=='GET')adminWrites.push({path,data:req.postDataJSON()});
|
||
|
|
if(path.endsWith('/admin/pin')){admin.pin_set=true;result={ok:true};}
|
||
|
|
if(path.endsWith('/admin/remotes')){admin.remotes=[{id:'test-id',name:'Arbejdsmobil'}];result={id:'test-id',code:'000000000042'};}
|
||
|
|
if(path.endsWith('/rotate'))result={id:'test-id',code:'000000000043'};
|
||
|
|
if(path.endsWith('/follow')){
|
||
|
|
const data=req.postDataJSON();
|
||
|
|
if(data.enable){follower=data.tab_id;result={following:true,sequence:seq};}
|
||
|
|
else if(data.disable){if(follower===data.tab_id)follower=null;result={following:false,sequence:seq};}
|
||
|
|
else result={following:follower===data.tab_id,sequence:seq,selection:follower===data.tab_id&&data.after<seq?{case_id:12}:null};
|
||
|
|
}
|
||
|
|
return route.fulfill({contentType:'application/json',body:JSON.stringify(result)});
|
||
|
|
});
|
||
|
|
page.on('pageerror',e=>errors.push(e.message));
|
||
|
|
await page.goto('http://remote.test/remote/manage');
|
||
|
|
await page.locator('#pin').fill('1234');await page.getByRole('button',{name:'Gem PIN',exact:true}).click();
|
||
|
|
await page.waitForFunction(()=>document.querySelector('#pin').value==='');
|
||
|
|
await page.locator('#name').fill('Arbejdsmobil');await page.getByRole('button',{name:'Opret og vis kode',exact:true}).click();
|
||
|
|
await page.locator('#new-code').waitFor({state:'visible'});
|
||
|
|
assert.equal(await page.locator('#code').textContent(),'000000000042');
|
||
|
|
await page.getByRole('button',{name:'Skjul kode',exact:true}).click();
|
||
|
|
assert.equal(await page.locator('#code').textContent(),'');
|
||
|
|
assert.equal(adminWrites.filter(x=>x.path.endsWith('/admin/pin')).length,1);
|
||
|
|
await page.goto('http://remote.test/');
|
||
|
|
await page.waitForSelector('button', {timeout:5000}).catch(error => { console.error('Hub script errors:',errors); throw error; });
|
||
|
|
assert.equal(await page.locator('button').first().textContent(),'Følg i denne fane', JSON.stringify(errors));
|
||
|
|
await page.getByRole('button',{name:'Følg i denne fane',exact:true}).click();
|
||
|
|
await page.getByRole('button',{name:'Stop med at følge',exact:true}).waitFor();
|
||
|
|
const firstId=follower;
|
||
|
|
const second=await context.newPage();second.on('pageerror',e=>errors.push(e.message));
|
||
|
|
await second.goto('http://remote.test/');
|
||
|
|
assert.equal(follower,firstId);
|
||
|
|
await second.getByRole('button',{name:'Følg i denne fane',exact:true}).click();
|
||
|
|
await second.getByRole('button',{name:'Stop med at følge',exact:true}).waitFor();
|
||
|
|
assert.notEqual(follower,firstId);
|
||
|
|
await page.bringToFront();
|
||
|
|
await page.getByRole('button',{name:'Følg i denne fane',exact:true}).waitFor();
|
||
|
|
await second.bringToFront();
|
||
|
|
seq++;
|
||
|
|
await second.waitForURL('**/sag/12');
|
||
|
|
assert.equal(page.url(),'http://remote.test/');
|
||
|
|
assert.deepEqual(errors,[]);
|
||
|
|
await context.close();
|
||
|
|
console.log(`${engine}: administration, kode, PIN og kun valgt Hub-fane OK`);
|
||
|
|
} finally { await browser.close(); }
|
||
|
|
})().catch(error=>{console.error(error);process.exitCode=1;});
|