Compétence Interactive Playwright
Utilisez une session Playwright js_repl persistante pour déboguer des applications web ou Electron locales, gardez les mêmes handles actifs entre les itérations, et exécutez l'assurance qualité fonctionnelle et visuelle sans redémarrer l'intégralité de la chaîne d'outils sauf si la propriété du processus a changé.
Préconditions
js_repldoit être activé pour cette compétence.- Si
js_replest manquant, activez-le dans~/.codex/config.toml:
[features]
js_repl = true
- Vous pouvez également démarrer une nouvelle session avec
--enable js_repl(équivalent à-c features.js_repl=true). - Après activation de
js_repl, démarrez une nouvelle session Codex pour que la liste des outils se rafraîchisse. - Pour l'instant, exécutez ce workflow avec le sandboxing désactivé : démarrez Codex avec
--sandbox danger-full-access(ou la configuration équivalente poursandbox_mode=danger-full-access). C'est une exigence temporaire pendant que le support dejs_repl+ Playwright dans le bac à sable est encore en cours de finalisation. - Exécutez la configuration à partir du même répertoire de projet que celui que vous devez déboguer.
- Traitez
js_repl_resetcomme un outil de récupération, non comme un nettoyage de routine. Réinitialiser le noyau détruit vos handles Playwright.
Configuration unique
test -f package.json || npm init -y
npm install playwright
# Web uniquement, pour Chromium en mode headed ou émulation mobile :
# npx playwright install chromium
# Electron uniquement, et seulement si l'espace de travail cible est l'application elle-même :
# npm install --save-dev electron
node -e "import('playwright').then(() => console.log('playwright import ok')).catch((error) => { console.error(error); process.exit(1); })"
Si vous basculez vers un autre espace de travail ultérieurement, répétez la configuration là-bas.
Workflow principal
- Rédigez un bref inventaire d'assurance qualité avant de tester :
- Construisez l'inventaire à partir de trois sources : les exigences demandées par l'utilisateur, les fonctionnalités ou comportements visibles par l'utilisateur que vous avez réellement implémentés, et les affirmations que vous vous attendez à faire dans la réponse finale.
- Tout ce qui apparaît dans l'une de ces trois sources doit être mappé à au moins une vérification d'assurance qualité avant la validation.
- Listez les affirmations visibles par l'utilisateur sur lesquelles vous envisagez de valider.
- Listez tous les contrôles visibles par l'utilisateur, les commutateurs de mode ou les comportements interactifs implémentés significatifs.
- Listez les changements d'état ou de vue que chaque contrôle ou comportement implémenté peut entraîner.
- Utilisez ceci comme liste de couverture partagée pour l'assurance qualité fonctionnelle et visuelle.
- Pour chaque affirmation ou paire contrôle-état, notez la vérification fonctionnelle prévue, l'état spécifique où la vérification visuelle doit se faire, et la preuve que vous vous attendez à capturer.
- Si une exigence est visuellement centrale mais subjective, convertissez-la en vérification d'assurance qualité observable plutôt que de la laisser implicite.
- Ajoutez au moins 2 scénarios exploratoires ou hors du chemin heureux qui pourraient exposer un comportement fragile.
- Exécutez la cellule bootstrap une seule fois.
- Démarrez ou confirmez tout serveur de développement requis dans une session TTY persistante.
- Lancez le runtime correct et continuez à réutiliser les mêmes handles Playwright.
- Après chaque changement de code, rechargez pour les changements du moteur de rendu ou relancez pour les changements du processus principal/démarrage.
- Exécutez l'assurance qualité fonctionnelle avec une entrée utilisateur normale.
- Exécutez une passe d'assurance qualité visuelle séparée.
- Vérifiez l'ajustement à la fenêtre et capturez les captures d'écran nécessaires pour soutenir vos affirmations.
- Nettoyez la session Playwright uniquement quand la tâche est réellement terminée.
Bootstrap (À exécuter une seule fois)
var chromium;
var electronLauncher;
var browser;
var context;
var page;
var mobileContext;
var mobilePage;
var electronApp;
var appWindow;
try {
({ chromium, _electron: electronLauncher } = await import("playwright"));
console.log("Playwright loaded");
} catch (error) {
throw new Error(
`Could not load playwright from the current js_repl cwd. Run the setup commands from this workspace first. Original error: ${error}`
);
}
Règles de liaison :
- Utilisez
varpour les handles Playwright partagés de niveau supérieur, car les cellulesjs_replultérieures les réutilisent. - Les cellules de configuration ci-dessous sont intentionnellement des chemins heureux courts. Si un handle semble obsolète, définissez cette liaison à
undefinedet réexécutez la cellule au lieu d'ajouter une logique de récupération partout. - Préférez un handle nommé par surface qui vous intéresse (
page,mobilePage,appWindow) plutôt que de redécouvrir les pages du contexte à plusieurs reprises.
Helpers web partagés :
var resetWebHandles = function () {
context = undefined;
page = undefined;
mobileContext = undefined;
mobilePage = undefined;
};
var ensureWebBrowser = async function () {
if (browser && !browser.isConnected()) {
browser = undefined;
resetWebHandles();
}
browser ??= await chromium.launch({ headless: false });
return browser;
};
var reloadWebContexts = async function () {
for (const currentContext of [context, mobileContext]) {
if (!currentContext) continue;
for (const p of currentContext.pages()) {
await p.reload({ waitUntil: "domcontentloaded" });
}
}
console.log("Reloaded existing web tabs");
};
Choisir le mode de session
Pour les applications web, utilisez une fenêtre d'affichage explicite par défaut et traitez le mode fenêtre native comme une passe de validation séparée.
- Utilisez une fenêtre d'affichage explicite pour l'itération de routine, les vérifications de points d'arrêt, les captures d'écran reproductibles, les diffs de snapshots et la localisation assistée par modèle. C'est la valeur par défaut car elle est stable entre les machines et évite la variabilité du gestionnaire de fenêtres hôte.
- Quand vous avez besoin d'un comportement haute résolution déterministe, gardez la fenêtre d'affichage explicite et ajoutez
deviceScaleFactorplutôt que de basculer directement vers le mode fenêtre native. - Utilisez le mode fenêtre native (
viewport: null) pour une passe headed séparée quand vous devez valider la taille de la fenêtre lancée, le comportement PPP au niveau du système d'exploitation, les interactions du navigateur ou les bogues qui peuvent dépendre de la configuration d'affichage de l'hôte. - Pour Electron, supposez un comportement fenêtre native tout le temps. Electron se lance via Playwright avec
noDefaultViewport, traitez-le comme une vraie fenêtre de bureau et vérifiez la taille et la disposition au lancement avant de redimensionner quoi que ce soit. - Quand la validation dépend à la fois des points d'arrêt de disposition et du comportement réel du bureau, faites les deux passes : fenêtre d'affichage explicite d'abord pour l'assurance qualité déterministe, puis validation fenêtre native pour les vérifications finales spécifiques à l'environnement.
- Traitez le changement de mode comme une réinitialisation de contexte. Ne réutilisez pas un contexte émulé avec fenêtre d'affichage pour une passe fenêtre native ou vice versa ; fermez l'ancienne
pageetcontext, puis créez-en une nouvelle pour le nouveau mode.
Démarrer ou réutiliser une session web
Les sessions web de bureau et mobiles partagent le même browser, les mêmes helpers et le même flux d'assurance qualité. La principale différence est la paire contexte et page que vous créez.
Contexte web de bureau
Définissez TARGET_URL sur l'application que vous déboguez. Pour les serveurs locaux, préférez 127.0.0.1 à localhost.
var TARGET_URL = "http://127.0.0.1:3000";
if (page?.isClosed()) page = undefined;
await ensureWebBrowser();
context ??= await browser.newContext({
viewport: { width: 1600, height: 900 },
});
page ??= await context.newPage();
await page.goto(TARGET_URL, { waitUntil: "domcontentloaded" });
console.log("Loaded:", await page.title());
Si context ou page est obsolète, définissez context = page = undefined et réexécutez la cellule.
Contexte web mobile
Réutilisez TARGET_URL s'il existe déjà ; sinon, définissez directement une cible mobile.
var MOBILE_TARGET_URL = typeof TARGET_URL === "string"
? TARGET_URL
: "http://127.0.0.1:3000";
if (mobilePage?.isClosed()) mobilePage = undefined;
await ensureWebBrowser();
mobileContext ??= await browser.newContext({
viewport: { width: 390, height: 844 },
isMobile: true,
hasTouch: true,
});
mobilePage ??= await mobileContext.newPage();
await mobilePage.goto(MOBILE_TARGET_URL, { waitUntil: "domcontentloaded" });
console.log("Loaded mobile:", await mobilePage.title());
Si mobileContext ou mobilePage est obsolète, définissez mobileContext = mobilePage = undefined et réexécutez la cellule.
Passe web en mode fenêtre native
var TARGET_URL = "http://127.0.0.1:3000";
await ensureWebBrowser();
await page?.close().catch(() => {});
await context?.close().catch(() => {});
page = undefined;
context = undefined;
browser ??= await chromium.launch({ headless: false });
context = await browser.newContext({ viewport: null });
page = await context.newPage();
await page.goto(TARGET_URL, { waitUntil: "domcontentloaded" });
console.log("Loaded native window:", await page.title());
Démarrer ou réutiliser une session Electron
Définissez ELECTRON_ENTRY sur . quand l'espace de travail actuel est l'application Electron et que package.json pointe main vers le bon fichier d'entrée. Si vous devez cibler un fichier de processus principal spécifique directement, utilisez un chemin comme ./main.js à la place.
var ELECTRON_ENTRY = ".";
if (appWindow?.isClosed()) appWindow = undefined;
if (!appWindow && electronApp) {
await electronApp.close().catch(() => {});
electronApp = undefined;
}
electronApp ??= await electronLauncher.launch({
args: [ELECTRON_ENTRY],
});
appWindow ??= await electronApp.firstWindow();
console.log("Loaded Electron window:", await appWindow.title());
Si js_repl ne s'exécute pas déjà à partir de l'espace de travail de l'application Electron, passez cwd explicitement lors du lancement.
Si le processus de l'application semble obsolète, définissez electronApp = appWindow = undefined et réexécutez la cellule.
Si vous avez déjà une session Electron mais vous avez besoin d'un processus frais après une modification du processus principal, du préchargement ou du démarrage, utilisez la cellule de relance dans la section suivante au lieu de réexécuter celle-ci.
Réutiliser les sessions pendant l'itération
Gardez la même session active quand vous le pouvez.
Rechargement du moteur de rendu web :
await reloadWebContexts();
Rechargement du moteur de rendu Electron uniquement :
await appWindow.reload({ waitUntil: "domcontentloaded" });
console.log("Reloaded Electron window");
Relance d'Electron après les modifications du processus principal, du préchargement ou du démarrage :
await electronApp.close().catch(() => {});
electronApp = undefined;
appWindow = undefined;
electronApp = await electronLauncher.launch({
args: [ELECTRON_ENTRY],
});
appWindow = await electronApp.firstWindow();
console.log("Relaunched Electron window:", await appWindow.title());
Si votre lancement nécessite un cwd explicite, incluez le même cwd ici.
Position par défaut :
- Gardez chaque cellule
js_replcourte et concentrée sur une rafale d'interaction. - Réutilisez les liaisons de niveau supérieur existantes (
browser,context,page,electronApp,appWindow) au lieu de les redéclarer. - Si vous avez besoin d'isolation, créez une nouvelle page ou un nouveau contexte au sein du même navigateur.
- Pour Electron, utilisez
electronApp.evaluate(...)uniquement pour l'inspection du processus principal ou les diagnostiques spécialisés. - Corrigez les erreurs de helper sur place ; ne réinitialisez pas le REPL à moins que le noyau ne soit réellement défaillant.
Listes de vérification
Boucle de session
- Amorce
js_replune seule fois, puis gardez les mêmes handles Playwright actifs entre les itérations. - Lancez le runtime cible à partir de l'espace de travail actuel.
- Apportez le changement de code.
- Rechargez ou relancez en utilisant le chemin correct pour ce changement.
- Mettez à jour l'inventaire d'assurance qualité partagé si l'exploration révèle un contrôle, un état ou une affirmation visible supplémentaire.
- Réexécutez l'assurance qualité fonctionnelle.
- Réexécutez l'assurance qualité visuelle.
- Capturez les artefacts finaux uniquement après que l'état actuel soit celui que vous évaluez.
Décision de rechargement
- Changement du moteur de rendu uniquement : rechargez la page existante ou la fenêtre Electron.
- Modification du processus principal, du préchargement ou du démarrage : relancez Electron.
- Nouvelle incertitude concernant la propriété du processus ou le code de démarrage : relancez plutôt que de deviner.
Assurance qualité fonctionnelle
- Utilisez des contrôles utilisateur réels pour la validation : clavier, souris, clic, toucher ou API d'entrée Playwright équivalente.
- Vérifiez au moins un flux critique de bout en bout.
- Confirmez le résultat visible de ce flux, non seulement l'état interne.
- Pour les applications en temps réel ou lourdes en animations, vérifiez le comportement dans le timing d'interaction réel.
- Travaillez à travers l'inventaire d'assurance qualité partagé plutôt que des vérifications ponctuelles ad hoc.
- Couvrez tous les contrôles visibles évidents au moins une fois avant la validation, non seulement le chemin heureux principal.
- Pour les contrôles réversibles ou les bascules avec état dans l'inventaire, testez le cycle complet : état initial, état modifié et retour à l'état initial.
- Après que les vérifications scriptées réussissent, faites une courte passe exploratoire en utilisant une entrée normale pendant 30-90 secondes au lieu de suivre seulement le chemin prévu.
- Si la passe exploratoire révèle un nouvel état, contrôle ou affirmation, ajoutez-le à l'inventaire d'assurance qualité partagé et couvrez-le avant la validation.
page.evaluate(...)etelectronApp.evaluate(...)peuvent inspecter ou préparer l'état, mais ils ne comptent pas comme entrée de validation.
Assurance qualité visuelle
- Traitez l'assurance qualité visuelle comme séparée de l'assurance qualité fonctionnelle.
- Utilisez le même inventaire d'assurance qualité partagé défini avant les tests et mis à jour pendant l'assurance qualité ; ne commencez pas la couverture visuelle à partir d'une liste implicite différente.
- Reformulez les affirmations visibles par l'utilisateur et vérifiez chacune explicitement ; ne supposez pas qu'une passe fonctionnelle prouve une affirmation visuelle.
- Une affirmation visible par l'utilisateur n'est pas validée tant qu'elle n'a pas été inspectée dans l'état spécifique où elle est destinée à être perçue.
- Inspectez la fenêtre d'affichage initiale avant de faire défiler.
- Confirmez que la vue initiale supporte visuellement les affirmations principales de l'interface ; si un élément essentiel promis n'est pas clairement perceptible là, traitez cela comme un bogue.
- Inspectez toutes les régions visibles requises, non seulement la surface d'interaction principale.
- Inspectez les états et modes déjà énumérés dans l'inventaire d'assurance qualité partagé, incluant au moins un état post-interaction significatif quand la tâche est interactive.
- Si le mouvement ou les transitions font partie de l'expérience, inspectez au moins un état en-transition en plus des points finaux établis.
- Si les étiquettes, les superpositions, les annotations, les guides ou les surbrillances sont destinés à suivre le contenu changeant, vérifiez cette relation après le changement d'état pertinent.
- Pour les visuels dynamiques ou dépendant de l'interaction, inspectez assez longtemps pour juger la stabilité, la superposition et la lisibilité ; ne comptez pas sur une seule capture d'écran pour la validation.
- Pour les interfaces qui peuvent devenir plus denses après le chargement ou l'interaction, inspectez l'état le plus dense et réaliste que vous pouvez atteindre pendant l'assurance qualité, non seulement l'état vide, chargement ou réduit.
- Si le produit a une fenêtre d'affichage ou une taille de fenêtre minimale supportée définie, exécutez une passe d'assurance qualité visuelle séparée là ; sinon, choisissez une taille plus petite mais toujours réaliste et inspectez-la explicitement.
- Distinguez la présence de l'implémentation : si une affordance prévue est techniquement là mais n'est pas clairement perceptible à cause d'un faible contraste, d'une occlusion, d'un découpage ou d'une instabilité, traitez cela comme un échec visuel.
- Si une région visible requise est découpée, coupée, obscurcie ou poussée hors de la fenêtre d'affichage dans l'état que vous évaluez, traitez cela comme un bogue même si les métriques de défilement au niveau de la page semblent acceptables.
- Recherchez le découpage, le débordement, la distorsion, le déséquilibre de mise en page, l'espacement incohérent, les problèmes d'alignement, le texte illisible, le faible contraste, la rupture de superposition et les états de mouvement maladroits.
- Jugez la qualité esthétique ainsi que la correction. L'interface doit sembler intentionnelle, cohérente et visuellement agréable pour la tâche.
- Préférez les captures d'écran de fenêtre d'affichage pour la validation. Utilisez les captures pleine page uniquement comme artefacts de débogage secondaires, et capturez une capture d'écran concentrée quand une région a besoin d'une inspection plus proche.
- Si le mouvement rend une capture d'écran ambiguë, attendez brièvement que l'interface se stabilise, puis capturez l'image que vous évaluez réellement.
- Avant la validation, demandez explicitement : quelle partie visible de cette interface n'ai-je pas encore inspectée de près ?
- Avant la validation, demandez explicitement : quel défaut visible embarrasserait le plus ce résultat si l'utilisateur regardait de près ?
Validation
- Le chemin fonctionnel a réussi avec une entrée utilisateur normale.
- La couverture est explicite contre l'inventaire d'assurance qualité partagé : notez quelles exigences, fonctionnalités implémentées, contrôles, états et affirmations ont été exercés, et appelez les exclusions intentionnelles.
- La passe d'assurance qualité visuelle a couvert l'interface entière pertinente.
- Chaque affirmation visible par l'utilisateur a une vérification visuelle correspondante et un artefact de capture d'écran examiné à partir de l'état et de la fenêtre d'affichage ou de la taille de fenêtre où cette affirmation importe.
- Les vérifications d'ajustement à la fenêtre d'affichage ont réussi pour la vue initiale prévue et toute taille de fenêtre ou de fenêtre minimale supportée requise.
- Si le produit se lance dans une fenêtre, la taille, le placement et la disposition initiale au lancement ont été vérifiés avant tout redimensionnement ou repositionnement manuel.
- L'interface n'est pas seulement fonctionnelle ; elle est visuellement cohérente et non esthétiquement faible pour la tâche.
- La correction fonctionnelle, l'ajustement à la fenêtre d'affichage et la qualité visuelle doivent chacun réussir par leur compte ; l'un n'implique pas les autres.
- Une courte passe exploratoire a été complétée pour les produits interactifs, et la réponse mentionne ce que cette passe a couvert.
- Si l'examen des captures d'écran et les vérifications numériques ne concordaient pas à un moment donné, la discordance a été enquêtée avant la validation ; le découpage visible dans les captures d'écran est un échec à résoudre, non quelque chose que les métriques peuvent dépasser.
- Incluez une brève confirmation négative des principales classes de défauts que vous avez vérifiées et que vous n'avez pas trouvées.
- Le nettoyage a été exécuté, ou vous avez intentionnellement gardé la session active pour des travaux ultérieurs.
Exemples de captures d'écran
Si vous prévoyez d'émettre une capture d'écran via codex.emitImage(...), utilisez les chemins normalisés en CSS dans la section suivante par défaut. Ce sont les exemples canoniques pour les captures d'écran qui seront interprétées par le modèle ou utilisées pour les actions de suivi basées sur les coordonnées. Gardez les captures brutes comme une exception pour le débogage sensible à la fidélité uniquement ; les exemples d'exception bruts apparaissent après la guidance de normalisation.
Captures d'écran liées aux modèles (par défaut)
Si vous allez émettre une capture d'écran avec codex.emitImage(...) pour l'interprétation du modèle, normalisez-la aux pixels CSS pour la région exacte que vous avez capturée avant d'émettre. Cela garde les coordonnées retournées alignées avec les pixels CSS de Playwright si la réponse est utilisée ultérieurement pour cliquer, et réduit également la taille de la charge utile d'image et le coût du token du modèle.
N'émettez pas les captures d'écran de fenêtre native brutes par défaut. Ne sautez la normalisation que si vous avez explicitement besoin de la fidélité des pixels de l'appareil, comme le débogage des artefacts Retina ou PPP, l'inspection du rendu précis au pixel, ou un autre cas sensible à la fidélité où les pixels bruts importent plus que la taille de la charge utile. Pour l'inspection locale uniquement qui n'sera pas émise au modèle, la capture brute est acceptable.
Ne supposez pas que page.screenshot({ scale: "css" }) est suffisant en mode fenêtre native (viewport: null). Sur Chromium sur les écrans Retina macOS, les captures d'écran de fenêtre native headed peuvent toujours revenir à la taille des pixels de l'appareil même quand scale: "css" est demandé. La même mise en garde s'applique aux fenêtres Electron lancées via Playwright car Electron s'exécute avec noDefaultViewport, et appWindow.screenshot({ scale: "css" }) peut toujours retourner une sortie au pixel de l'appareil.
Utilisez des chemins de normalisation séparés pour les pages web et les fenêtres Electron :
- Web : préférez directement
page.screenshot({ scale: "css" }). Si Chromium en mode fenêtre native retourne toujours une sortie au pixel de l'appareil, redimensionnez à l'intérieur de la page actuelle avec canvas ; aucune page de scratch n'est requise. - Electron : n'utilisez pas
appWindow.context().newPage()ouelectronApp.context().newPage()comme page de scratch. Les contextes Electron ne supportent pas ce chemin de manière fiable. Capturez dans le processus principal avecBrowserWindow.capturePage(...), redimensionnez avecnativeImage.resize(...), et émettez ces octets directement.
Helpers partagés et conventions :
var emitJpeg = async function (bytes) {
await codex.emitImage({
bytes,
mimeType: "image/jpeg",
detail: "original",
});
};
var emitWebJpeg = async function (surface, options = {}) {
await emitJpeg(await surface.screenshot({
type: "jpeg",
quality: 85,
scale: "css",
...options,
}));
};
var clickCssPoint = async function ({ surface, x, y, clip }) {
await surface.mouse.click(
clip ? clip.x + x : x,
clip ? clip.y + y : y
);
};
var tapCssPoint = async function ({ page, x, y, clip }) {
await page.touchscreen.tap(
clip ? clip.x + x : x,
clip ? clip.y + y : y
);
};
- Utilisez
pageoumobilePagepour le web, ouappWindowpour Electron, comme lasurface. - Traitez
clipcomme des pixels CSS degetBoundingClientRect()dans le moteur de rendu. - Préférez JPEG à
quality: 85sauf si la fidélité sans perte est spécifiquement requise. - Pour les captures d'image complète, utilisez directement le
{ x, y }retourné. - Pour les captures découpées, rajoutez l'origine du clip lors du clic.
Normalisation CSS web
Chemin web préféré pour les contextes avec fenêtre d'affichage explicite, et souvent pour le web en général :
await emitWebJpeg(page);
Le web mobile utilise le même chemin ; substituez mobilePage pour page :
await emitWebJpeg(mobilePage);
Si le modèle retourne { x, y }, cliquez dessus directement :
await clickCssPoint({ surface: page, x, y });
Chemin de clic web mobile :
await tapCssPoint({ page: mobilePage, x, y });
Pour les captures d'écran web clip ou les captures d'élément dans ce chemin normal, scale: "css" fonctionne généralement directement. Rajoutez l'origine de la région lors du clic.
await emitWebJpeg(page, { clip })await emitWebJpeg(mobilePage, { clip })await clickCssPoint({ surface: page, clip, x, y })await tapCssPoint({ page: mobilePage, clip, x, y })await clickCssPoint({ surface: page, clip: box, x, y })aprèsconst box = await locator.boundingBox()
Fallback web en mode fenêtre native quand scale: "css" revient toujours à la taille des pixels de l'appareil :
var emitWebScreenshotCssScaled = async function ({ page, clip, quality = 0.85 } = {}) {
var NodeBuffer = (await import("node:buffer")).Buffer;
const target = clip
? { width: clip.width, height: clip.height }
: await page.evaluate(() => ({
width: window.innerWidth,
height: window.innerHeight,
}));
const screenshotBuffer = await page.screenshot({
type: "png",
...(clip ? { clip } : {}),
});
const bytes = await page.evaluate(
async ({ imageBase64, targetWidth, targetHeight, quality }) => {
const image = new Image();
image.src = `data:image/png;base64,${imageBase64}`;
await image.decode();
const canvas = document.createElement("canvas");
canvas.width = targetWidth;
canvas.height = targetHeight;
const ctx = canvas.getContext("2d");
ctx.imageSmoothingEnabled = true;
ctx.drawImage(image, 0, 0, targetWidth, targetHeight);
const blob = await new Promise((resolve) =>
canvas.toBlob(resolve, "image/jpeg", quality)
);
return new Uint8Array(await blob.arrayBuffer());
},
{
imageBase64: NodeBuffer.from(screenshotBuffer).toString("base64"),
targetWidth: target.width,
targetHeight: target.height,
quality,
}
);
await emitJpeg(bytes);
};
Pour une capture fallback de fenêtre d'affichage complète, traitez le { x, y } retourné comme des coordonnées CSS directes :
await emitWebScreenshotCssScaled({ page });
await clickCssPoint({ surface: page, x, y });
Pour une capture fallback découpée, rajoutez l'origine du clip :
await emitWebScreenshotCssScaled({ page, clip });
await clickCssPoint({ surface: page, clip, x, y });
Normalisation CSS Electron
Pour Electron, normalisez dans le processus principal au lieu d'ouvrir une page Playwright de scratch. Le helper ci-dessous retourne des octets à l'échelle CSS pour la zone de contenu entière ou pour une région CSS-pixel découpée. Traitez clip comme des pixels CSS de zone de contenu, par exemple des valeurs prises de getBoundingClientRect() dans le moteur de rendu.
var emitElectronScreenshotCssScaled = async function ({ electronApp, clip, quality = 85 } = {}) {
const bytes = await electronApp.evaluate(async ({ BrowserWindow }, { clip, quality }) => {
const win = BrowserWindow.getAllWindows()[0];
const image = clip ? await win.capturePage(clip) : await win.capturePage();
const target = clip
? { width: clip.width, height: clip.height }
: (() => {
const [width, height] = win.getContentSize();
return { width, height };
})();
const resized = image.resize({
width: target.width,
height: target.height,
quality: "best",
});
return resized.toJPEG(quality);
}, { clip, quality });
await emitJpeg(bytes);
};
Fenêtre Electron complète :
await emitElectronScreenshotCssScaled({ electronApp });
await clickCssPoint({ surface: appWindow, x, y });
Région Electron découpée en utilisant les pixels CSS du moteur de rendu :
var clip = await appWindow.evaluate(() => {
const rect = document.getElementById("board").getBoundingClientRect();
return {
x: Math.round(rect.x),
y: Math.round(rect.y),
width: Math.round(rect.width),
height: Math.round(rect.height),
};
});
await emitElectronScreenshotCssScaled({ electronApp, clip });
await clickCssPoint({ surface: appWindow, clip, x, y });
Exemples d'exception de capture brute
Utilisez ceci seulement quand les pixels bruts importent plus que l'alignement des coordonnées CSS, comme le débogage des artefacts Retina ou PPP, l'inspection du rendu précis au pixel, ou un autre examen sensible à la fidélité.
Émission brute web de bureau :
await codex.emitImage({
bytes: await page.screenshot({ type: "jpeg", quality: 85 }),
mimeType: "image/jpeg",
detail: "original",
});
Émission brute Electron :
await codex.emitImage({
bytes: await appWindow.screenshot({ type: "jpeg", quality: 85 }),
mimeType: "image/jpeg",
detail: "original",
});
Émission brute web mobile après que le contexte web mobile s'exécute déjà :
await codex.emitImage({
bytes: await mobilePage.screenshot({ type: "jpeg", quality: 85 }),
mimeType: "image/jpeg",
detail: "original",
});
Vérifications d'ajustement à la fenêtre d'affichage (Requises)
Ne supposez pas qu'une capture d'écran est acceptable simplement parce que le widget principal est visible. Avant la validation, vérifiez explicitement que la vue initiale prévue correspond aux exigences du produit, en utilisant à la fois l'examen des captures d'écran et les vérifications numériques.
- Définissez la vue initiale prévue avant la validation. Pour les pages déroulables, c'est l'expérience au-dessus du pli. Pour les coques d'application, les jeux, les éditeurs, les tableaux de bord ou les outils, c'est la surface interactive complète plus les contrôles et le statut nécessaires pour l'utiliser.
- Utilisez les captures d'écran comme preuve principale de l'ajustement. Les vérifications numériques soutiennent les captures d'écran ; elles ne les remplacent pas.
- La validation échoue si une région visible requise est découpée, coupée, obscurcie ou poussée hors de la fenêtre d'affichage dans la vue initiale prévue, même si les métriques de défilement au niveau de la page semblent acceptables.
- Le défilement est acceptable quand le produit est conçu pour défiler et la vue initiale communique toujours l'expérience centrale et expose l'appel à l'action principal ou le contexte de démarrage requis.
- Pour les interfaces à coques fixes, le défilement n'est pas un contournement acceptable s'il est nécessaire pour atteindre une partie de la surface interactive principale ou des contrôles essentiels.
- Ne comptez pas sur les métriques de défilement de document seuls. Les coques à hauteur fixe, les panneaux internes et les conteneurs avec débordement masqué peuvent découper l'interface utilisateur requise pendant que les vérifications de défilement au niveau de la page ressemblent toujours correctes.
- Vérifiez les limites des régions, non seulement les limites du document. Vérifiez que chaque région visible requise s'ajuste dans la fenêtre d'affichage dans l'état de démarrage.
- Pour Electron ou les applications de bureau, vérifiez à la fois la taille et le placement de la fenêtre lancée et la mise en page visible initiale du moteur de rendu avant tout redimensionnement ou repositionnement manuel.
- Passer les vérifications d'ajustement à la fenêtre d'affichage prouve seulement que la vue initiale prévue est visible sans découpage involontaire ou défilement. Cela ne prouve pas que l'interface utilisateur est correcte visuellement ou esthétiquement réussie.
Vérification web ou moteur de rendu :
console.log(await page.evaluate(() => ({
innerWidth: window.innerWidth,
innerHeight: window.innerHeight,
clientWidth: document.documentElement.clientWidth,
clientHeight: document.documentElement.clientHeight,
scrollWidth: document.documentElement.scrollWidth,
scrollHeight: document.documentElement.scrollHeight,
canScrollX: document.documentElement.scrollWidth > document.documentElement.clientWidth,
canScrollY: document.documentElement.scrollHeight > document.documentElement.clientHeight,
})));
Vérification Electron :
console.log(await appWindow.evaluate(() => ({
innerWidth: window.innerWidth,
innerHeight: window.innerHeight,
clientWidth: document.documentElement.clientWidth,
clientHeight: document.documentElement.clientHeight,
scrollWidth: document.documentElement.scrollWidth,
scrollHeight: document.documentElement.scrollHeight,
canScrollX: document.documentElement.scrollWidth > document.documentElement.clientWidth,
canScrollY: document.documentElement.scrollHeight > document.documentElement.clientHeight,
})));
Augmentez la vérification numérique avec des vérifications getBoundingClientRect() pour les régions visibles requises dans votre interface utilisateur spécifique quand le découpage est un mode d'échec réaliste ; les métriques au niveau du document seuls ne sont pas suffisants pour les coques fixes.
Serveur de développement
Pour le débogage web local, gardez l'application en cours d'exécution dans une session TTY persistante. Ne comptez pas sur des commandes en arrière-plan à usage unique à partir d'un shell de courte durée.
Utilisez la commande start normale du projet, par exemple :
npm start
Avant page.goto(...), vérifiez que le port choisi écoute et que l'application répond.
Pour le débogage Electron, lancez l'application depuis js_repl via _electron.launch(...) pour que la même session possède le processus. Si le moteur de rendu Electron dépend d'un serveur de développement séparé (par exemple Vite ou Next), gardez ce serveur en cours d'exécution dans une session TTY persistante puis relancez ou rechargez l'application Electron depuis js_repl.
Nettoyage
Exécutez le nettoyage seulement quand la tâche est réellement terminée :
- Ce nettoyage est manuel. Quitter Codex, fermer le terminal, ou perdre la session
js_repln'exécute pas implicitementelectronApp.close(),context.close(), oubrowser.close(). - Pour Electron spécifiquement, supposez que l'application peut continuer à s'exécuter si vous quittez la session sans exécuter d'abord la cellule de nettoyage.
if (electronApp) {
await electronApp.close().catch(() => {});
}
if (mobileContext) {
await mobileContext.close().catch(() => {});
}
if (context) {
await context.close().catch(() => {});
}
if (browser) {
await browser.close().catch(() => {});
}
browser = undefined;
context = undefined;
page = undefined;
mobileContext = undefined;
mobilePage = undefined;
electronApp = undefined;
appWindow = undefined;
console.log("Playwright session closed");
Si vous prévoyez de quitter Codex immédiatement après le débogage, exécutez d'abord la cellule de nettoyage et attendez le journal "Playwright session closed" avant de quitter.
Modes d'échec courants
Cannot find module 'playwright': exécutez la configuration unique dans l'espace de travail actuel et vérifiez l'importation avant d'utiliserjs_repl.- Le paquet Playwright est installé mais l'exécutable du navigateur manque : exécutez
npx playwright install chromium. page.goto: net::ERR_CONNECTION_REFUSED: assurez-vous que le serveur de développement s'exécute toujours dans une session TTY persistante, revérifiez le port et préférezhttp://127.0.0.1:<port>.electron.launchpend, délai d'expiration ou quitte immédiatement : vérifiez la dépendance localeelectron, confirmez la cibleargs, et assurez-vous que tout serveur de développement du moteur de rendu s'exécute déjà avant le lancement.Identifier has already been declared: réutilisez les liaisons de niveau supérieur existantes, choisissez un nouveau nom, ou enveloppez le code dans{ ... }. Utilisezjs_repl_resetseulement quand le noyau est réellement coincé.browserContext.newPage: Protocol error (Target.createTarget): Not supportedlors du travail avec Electron : n'utilisez pasappWindow.context().newPage()ouelectronApp.context().newPage()comme page de scratch ; utilisez le flux de normalisation de capture d'écran spécifique à Electron dans la section des captures d'écran liées aux modèles.js_repla dépassé le délai d'attente ou a été réinitialisé : réexécutez la cellule bootstrap et recréez la session avec des cellules plus courtes et plus concentrées.- Le lancement du navigateur ou les opérations réseau échouent immédiatement : confirmez que la session a été démarrée avec
--sandbox danger-full-accesset redémarrez de cette manière si nécessaire.