Files
javis_bot/bot/scripts/stream-test/scenario.mjs
javis-bot 2cdd159fc1 feat(stream-test): drive the whole browse scenario with real input
Make every action real keyboard/mouse via xdotool, not just the visible
browsing: address-bar navigation (Ctrl+L + char-by-char typing), the YouTube
settings gear -> 화질 -> 1080p menu (real clicks, verified hd1080), the autoplay
toggle, the play button, and fullscreen via the real 'f' key (F11 isn't honored
by this WM; 'f' yields true 1080p fullscreen without pausing). CDP/DOM API is
now used only to read state for verification.
2026-06-10 14:11:58 +09:00

107 lines
4.9 KiB
JavaScript

// Browse scenario driven ENTIRELY with real mouse/keyboard input via xdotool
// (see human.mjs). Connects to a Chrome already running with
// --remote-debugging-port (default 9222) on the streamed X display.
//
// All ACTIONS are real input: address-bar navigation (Ctrl+L + typing),
// search typing, clicking the video, the settings gear -> 화질 -> 1080p menu,
// the autoplay toggle, the play button, fullscreen via the 'f' key, scrolling,
// and entering 나무위키. The CDP/DOM API is used ONLY to read state for
// verification (paused/quality/fullscreen) and as a rare click fallback when an
// element has no on-screen box.
import { chromium } from 'playwright';
import { humanClick, humanType, humanKey, humanHover, navigateOmnibox, humanScroll, sleep } from './human.mjs';
const CDP = process.env.CDP_PORT || '9222';
const VID = process.env.TEST_VIDEO_ID || 'X_am71G6Vy4';
const SEARCH = process.env.TEST_YT_QUERY || '내손을잡아';
const NAVER_Q = process.env.TEST_NAVER_QUERY || '아이유';
const b = await chromium.connectOverCDP(`http://localhost:${CDP}`);
const ctx = b.contexts()[0];
const page = ctx.pages()[0];
page.setDefaultTimeout(25000);
const read = (fn) => page.evaluate(fn);
const playerLoc = () => page.locator('#movie_player');
// 1) open YouTube by typing the URL in the address bar
await navigateOmnibox('https://www.youtube.com'); await sleep(3000);
// 2) really type the search and submit
await humanClick(page, page.locator('input#search, input[name=search_query]').first());
await humanType(SEARCH);
await humanKey('Return');
await sleep(3800);
// 3) click the IU concert result with the real mouse
let link = page.locator(`a#video-title[href*="${VID}"], a[href*="${VID}"]`).first();
if (!(await link.count().catch(() => 0))) link = page.locator('ytd-video-renderer a#video-title, ytd-rich-item-renderer a#video-title').first();
await humanClick(page, link);
await sleep(3500);
await page.waitForSelector('#movie_player', { timeout: 25000 }); await sleep(2000);
for (let i = 0; i < 8; i++) { const ad = page.locator('.ytp-ad-skip-button, .ytp-ad-skip-button-modern, .ytp-skip-ad-button'); if (await ad.count().catch(() => 0)) { await humanClick(page, ad.first()); await sleep(1200); } else break; }
// 4) if paused, press play with the real mouse
if (await read(() => document.querySelector('video')?.paused)) {
const big = page.locator('.ytp-large-play-button, .ytp-play-button').first();
await humanClick(page, big);
}
await sleep(1500);
// 5) set 1080p through the real settings menu (gear -> 화질 -> 1080p), verify
async function setQuality1080() {
for (let attempt = 0; attempt < 2; attempt++) {
await humanHover(page, playerLoc());
await humanClick(page, page.locator('.ytp-settings-button')); await sleep(900);
let qrow = page.locator('.ytp-menuitem', { hasText: /화질|Quality/ }).first();
if (!(await qrow.count().catch(() => 0))) qrow = page.locator('.ytp-panel-menu .ytp-menuitem').last();
await humanClick(page, qrow); await sleep(900);
const item = page.locator('.ytp-menuitem', { hasText: /1080/ }).first();
if (await item.count().catch(() => 0)) await humanClick(page, item);
await sleep(2000);
const q = await read(() => document.getElementById('movie_player')?.getPlaybackQuality?.());
if (q && /1080/.test(q)) return q;
}
return null;
}
console.log('QUALITY', await setQuality1080());
// 6) turn off autoplay with a real click if it is on
const auto = page.locator('.ytp-autonav-toggle-button');
if ((await auto.count().catch(() => 0)) && (await auto.getAttribute('aria-checked').catch(() => null)) === 'true') {
await humanHover(page, playerLoc());
await humanClick(page, auto);
}
console.log('STEP watch-1080-windowed'); await sleep(20000);
// 7) fullscreen with the real 'f' key (hover the player to focus it), 20s
await humanHover(page, playerLoc());
await humanKey('f'); await sleep(1500);
if (!(await read(() => !!document.fullscreenElement))) { await humanHover(page, playerLoc()); await humanKey('f'); await sleep(1200); }
console.log('STEP fullscreen', await read(() => ({ full: !!document.fullscreenElement, h: window.innerHeight }))); await sleep(20000);
// 8) exit fullscreen with real 'f'
await humanKey('f'); await sleep(1500);
// 9) Naver via the address bar, then really type the query
await navigateOmnibox('https://www.naver.com'); await sleep(2800);
await humanClick(page, page.locator('input#query').first());
await humanType(NAVER_Q);
await humanKey('Return');
await sleep(2800);
await humanScroll(page, +1, 18);
console.log('STEP naver-scrolled');
// 10) enter 나무위키 with a real click, then scroll
const namu = page.locator('a[href*="namu.wiki"]').first();
if (await namu.count().catch(() => 0)) {
await humanClick(page, namu);
await sleep(3000);
await humanScroll(page, +1, 14);
await humanScroll(page, -1, 8);
await humanScroll(page, +1, 10);
console.log('STEP namu-scrolled');
} else console.log('STEP namu-not-found');
console.log('SCENARIO_DONE');
await b.close();