92 lines
4.8 KiB
JavaScript
92 lines
4.8 KiB
JavaScript
|
|
// Critère de l'étape 15, dans de vrais navigateurs (Chromium, caméra et micro
|
|||
|
|
// simulés) : un prof et deux élèves rejoignent la visio ; un élève lève la
|
|||
|
|
// main ; le prof lui donne la parole ; un intrus sans jeton ne peut pas entrer.
|
|||
|
|
// node visio-e2e.mjs <fichier d'identifiants> [https://syllabuslab.fr]
|
|||
|
|
import { chromium } from 'playwright';
|
|||
|
|
import { readFileSync } from 'node:fs';
|
|||
|
|
|
|||
|
|
const ids = JSON.parse(readFileSync(process.argv[2], 'utf8'));
|
|||
|
|
const BASE = process.argv[3] ?? 'https://syllabuslab.fr';
|
|||
|
|
const S = ids.seance_id;
|
|||
|
|
const navigateur = await chromium.launch({ args: ['--use-fake-ui-for-media-stream', '--use-fake-device-for-media-stream'] });
|
|||
|
|
const contexte = () => navigateur.newContext({ locale: 'fr-FR', permissions: ['camera', 'microphone'] });
|
|||
|
|
const etape = (texte) => console.log(`${new Date().toISOString().slice(11, 19)} ${texte}`);
|
|||
|
|
const attendre = async (fn, delai = 30000, pas = 500) => {
|
|||
|
|
const fin = Date.now() + delai;
|
|||
|
|
while (Date.now() < fin) { const r = await fn(); if (r) return r; await new Promise((ok) => setTimeout(ok, pas)); }
|
|||
|
|
throw new Error('délai dépassé');
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
async function connexion(page, champs, bouton) {
|
|||
|
|
await page.goto(`${BASE}/connexion`);
|
|||
|
|
for (const [nom, valeur] of Object.entries(champs)) await page.fill(`input[name="${nom}"]`, valeur);
|
|||
|
|
await page.click(bouton);
|
|||
|
|
await page.waitForLoadState('networkidle');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 1. Le prof ouvre la visio.
|
|||
|
|
const prof = await (await contexte()).newPage();
|
|||
|
|
await connexion(prof, { email: ids.prof.email, password: ids.prof.mot_de_passe }, 'form[action$="/connexion/prof"] button[type=submit]');
|
|||
|
|
await prof.goto(`${BASE}/seances/${S}/visio`);
|
|||
|
|
if (await prof.locator('text=Ouvrir la visio').count()) await prof.click('text=Ouvrir la visio');
|
|||
|
|
await prof.waitForSelector('#v-scene iframe', { timeout: 30000 });
|
|||
|
|
etape('prof : visio ouverte, Jitsi chargé');
|
|||
|
|
|
|||
|
|
// 2. Deux élèves la rejoignent.
|
|||
|
|
const eleves = [];
|
|||
|
|
for (const e of ids.eleves) {
|
|||
|
|
const page = await (await contexte()).newPage();
|
|||
|
|
await connexion(page, { identifiant: e.identifiant, code: e.code }, 'form[action$="/connexion/eleve"] button[type=submit]');
|
|||
|
|
await page.goto(`${BASE}/seances/${S}/visio/eleve`);
|
|||
|
|
await page.waitForSelector('#v-scene iframe', { timeout: 30000 });
|
|||
|
|
eleves.push(page);
|
|||
|
|
}
|
|||
|
|
const enLigne = await attendre(async () => {
|
|||
|
|
const t = await prof.textContent('#v-nb');
|
|||
|
|
return /3 en ligne/.test(t ?? '') && t;
|
|||
|
|
}, 60000);
|
|||
|
|
etape(`prof : ${enLigne.trim()} (le prof et les deux élèves)`);
|
|||
|
|
|
|||
|
|
// 3. Léa lève la main ; elle apparaît chez le prof.
|
|||
|
|
const [lea, yanis] = eleves;
|
|||
|
|
await lea.click('[data-main-e]');
|
|||
|
|
await attendre(async () => (await prof.locator('#v-mains [data-parole]').count()) === 1, 30000);
|
|||
|
|
etape(`prof : main levée reçue de « ${(await prof.locator('#v-mains strong').first().textContent())} »`);
|
|||
|
|
|
|||
|
|
// 4. Yanis, sans la parole, ne peut pas réactiver son micro.
|
|||
|
|
await yanis.click('[data-micro-e]');
|
|||
|
|
await new Promise((ok) => setTimeout(ok, 4000));
|
|||
|
|
const statutYanis = (await yanis.textContent('[data-statut-e]')).trim();
|
|||
|
|
etape(`Yanis tente d'ouvrir son micro sans la parole → « ${statutYanis} »`);
|
|||
|
|
|
|||
|
|
// 5. Le prof donne la parole à Léa : elle peut ouvrir son micro.
|
|||
|
|
await prof.click('#v-mains [data-parole]');
|
|||
|
|
await attendre(async () => (await lea.getAttribute('[data-main-e]', 'aria-pressed')) === 'false', 30000);
|
|||
|
|
etape('Léa : sa main est baissée (parole donnée)');
|
|||
|
|
await lea.click('[data-micro-e]');
|
|||
|
|
const statutLea = await attendre(async () => {
|
|||
|
|
const t = (await lea.textContent('[data-statut-e]')).trim();
|
|||
|
|
return t === 'Tu as la parole' && t;
|
|||
|
|
}, 30000);
|
|||
|
|
etape(`Léa ouvre son micro → « ${statutLea} »`);
|
|||
|
|
|
|||
|
|
// 6. Un intrus, sans jeton, tente d'entrer directement dans la salle.
|
|||
|
|
const salle = await prof.evaluate(() => document.querySelector('#v-scene iframe').src.split('?')[0].split('#')[0].split('/').pop());
|
|||
|
|
const intrus = await (await contexte()).newPage();
|
|||
|
|
await intrus.goto(`${BASE.replace('://', '://meet.')}/${salle}`);
|
|||
|
|
await new Promise((ok) => setTimeout(ok, 12000));
|
|||
|
|
const toujours = (await prof.textContent('#v-nb')).trim();
|
|||
|
|
const ecranIntrus = (await intrus.locator('body').innerText()).replace(/\s+/g, ' ').slice(0, 160);
|
|||
|
|
etape(`intrus sans jeton : le prof voit toujours « ${toujours} » ; écran de l'intrus : « ${ecranIntrus} »`);
|
|||
|
|
|
|||
|
|
const ok = /3 en ligne/.test(toujours) && statutYanis === 'En sourdine' && statutLea === 'Tu as la parole';
|
|||
|
|
console.log(ok ? 'CRITÈRE DE L’ÉTAPE 15 : RÉUSSI' : 'CRITÈRE DE L’ÉTAPE 15 : ÉCHOUÉ');
|
|||
|
|
process.exitCode = ok ? 0 : 1;
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('ÉCHEC :', e.message);
|
|||
|
|
process.exitCode = 1;
|
|||
|
|
} finally {
|
|||
|
|
await navigateur.close();
|
|||
|
|
}
|