Files
maestro/src/engine/tools/browser.snapshot-secret.test.ts
T
oss-sync 57702b4f34
CI / build-and-test (push) Canceled after 0s
sync: update from private repo (2336949a)
2026-09-17 11:29:31 +00:00

143 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// @vitest-environment jsdom
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { SNAPSHOT_SCRIPT } from './browser.js';
/**
* SNAPSHOT_SCRIPT はページ内で eval される文字列。jsdom の上で実際に
* 走らせて、書き出しにパスワードが載らないことを見る(issue #1236)。
*
* jsdom の getBoundingClientRect は常に 0×0 を返すので、そのままだと
* isVisible() が全要素を落として何も走査されない。差し替えておく。
*/
let originalRect: () => DOMRect;
beforeAll(() => {
originalRect = Element.prototype.getBoundingClientRect;
Element.prototype.getBoundingClientRect = function (): DOMRect {
return { width: 100, height: 20, top: 0, left: 0, right: 100, bottom: 20, x: 0, y: 0, toJSON() {} } as DOMRect;
};
});
afterAll(() => {
Element.prototype.getBoundingClientRect = originalRect;
});
/** html を置いてから values を実際の .value に流し込み、書き出しを取る。 */
function snapshot(html: string, values: Record<string, string> = {}): string {
document.body.innerHTML = html;
for (const [id, v] of Object.entries(values)) {
(document.getElementById(id) as HTMLInputElement | HTMLTextAreaElement).value = v;
}
const result = (0, eval)(SNAPSHOT_SCRIPT) as { text: string };
return result.text;
}
describe('SNAPSHOT_SCRIPT — 入力欄の中身', () => {
it('type=password の欄は中身を書き出さず、値があることだけ残す', () => {
const text = snapshot('<input id="pw" type="password" name="password">', { pw: 'hunter2-topsecret' });
expect(text).not.toContain('hunter2-topsecret');
expect(text).toContain('value=[masked]');
});
it('name からパスワードと分かる欄も伏せる(type=text でも)', () => {
const text = snapshot('<input id="p" type="text" name="loginPassword">', { p: 'correct-horse-battery' });
expect(text).not.toContain('correct-horse-battery');
expect(text).toContain('value=[masked]');
});
it('autocomplete=one-time-code の欄も伏せる', () => {
const text = snapshot('<input id="c" type="text" autocomplete="one-time-code">', { c: '482913' });
expect(text).not.toContain('482913');
expect(text).toContain('value=[masked]');
});
it('空のパスワード欄には印を出さない(値の有無だけが残る)', () => {
const text = snapshot('<input id="pw" type="password" name="password">');
expect(text).not.toContain('[masked]');
});
it('名札だけが手掛かりの欄も伏せる(「表示」で伏せ字を解いた欄)', () => {
// type は text に変わり、id は枠組みの機械生成。残る手掛かりは名札だけ。
const text = snapshot('<input id=":r3:" type="text" aria-label="Password">', {
':r3:': 'correct-horse-battery',
});
expect(text).not.toContain('correct-horse-battery');
expect(text).toContain('value=[masked]');
});
it('`<label for>` が付いただけの欄も伏せる(伏せ字を解いた欄)', () => {
// type=text・name 無し・機械生成の id。手掛かりは離れた場所の名札だけ。
const text = snapshot('<label for=":r3:">パスワード</label><input id=":r3:" type="text">', {
':r3:': 'correct-horse-battery',
});
expect(text).not.toContain('correct-horse-battery');
expect(text).toContain('value=[masked]');
});
it('入れ子の `<label>` に包まれた欄も伏せる', () => {
const text = snapshot('<label>パスワード<input id=":r4:" type="text"></label>', {
':r4:': 'correct-horse-battery',
});
expect(text).not.toContain('correct-horse-battery');
expect(text).toContain('value=[masked]');
});
it('aria-labelledby で名札を指している欄も伏せる', () => {
const text = snapshot(
'<span id="lbl">パスワード</span><input id=":r5:" type="text" aria-labelledby="lbl">',
{ ':r5:': 'correct-horse-battery' },
);
expect(text).not.toContain('correct-horse-battery');
expect(text).toContain('value=[masked]');
});
it('離れた名札がふつうの語なら伏せない(伏せ過ぎの回帰)', () => {
const text = snapshot('<label for=":r6:">Shipping address</label><input id=":r6:" type="text">', {
':r6:': '東京都千代田区',
});
expect(text).toContain('value="東京都千代田区"');
});
it('日本語の名札でも伏せる', () => {
const text = snapshot('<input id="x1" type="text" placeholder="パスワード">', {
x1: 'correct-horse-battery',
});
expect(text).not.toContain('correct-horse-battery');
expect(text).toContain('value=[masked]');
});
it('ふつうの名札の欄は伏せない(伏せ過ぎの回帰)', () => {
const text = snapshot('<input id="q" type="text" aria-label="検索">', { q: 'ラーメン' });
expect(text).toContain('value="ラーメン"');
});
it('ふつうの入力欄はこれまでどおり中身が載る(伏せ過ぎの回帰)', () => {
const text = snapshot('<input id="u" type="text" name="username">', { u: 'alice' });
expect(text).toContain('value="alice"');
});
it('最初から中身が入っている textarea が秘密の欄なら、その文字を書き出さない', () => {
// textarea の中身は要素のテキストそのもの。value=[masked] だけ直しても、
// 名札の代わり(elementName)と本文の走査(walk)から同じ文字が 2 回出る。
const text = snapshot('<textarea id="ta" name="apiSecret">sk-live-PREFILLED-TEXTAREA-123</textarea>');
expect(text).not.toContain('sk-live-PREFILLED-TEXTAREA-123');
expect(text).toContain('value=[masked]');
});
it('ふつうの textarea の中身はこれまでどおり載る(伏せ過ぎの回帰)', () => {
const text = snapshot('<textarea id="c" name="comment">きょうの下書き-visible</textarea>');
expect(text).toContain('きょうの下書き-visible');
expect(text).not.toContain('[masked]');
});
it('パスワード欄とふつうの欄が並んでいても、伏せるのは片方だけ', () => {
const text = snapshot(
'<form><input id="u" name="username"><input id="pw" type="password" name="password"></form>',
{ u: 'alice', pw: 'hunter2-topsecret' },
);
expect(text).toContain('value="alice"');
expect(text).toContain('value=[masked]');
expect(text).not.toContain('hunter2-topsecret');
});
});