Files
maestro/src/engine/tools/browser.dumphtml-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

168 lines
7.6 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 } from 'vitest';
import { buildDumpHtmlScript } from './browser.js';
/**
* dumpHtml がページ内で走らせる文字列を jsdom の上で実際に評価して、
* 生の HTML に入力欄の中身が載らないことを見る(issue #1236 レビュー)。
*
* 入力した文字列を `value` **属性**へ書き戻すページ(React の制御された
* input など)では、打ち込んだ値がそのまま HTML に現れる。ここはその形を
* 直接置いて確かめる。ブラウザを通す経路の確認は
* browser.recording-secret.e2e.test.ts(http サーバ + 本物の Chromium)。
*/
function dump(html: string, selector?: string, depth = 3): string {
document.body.innerHTML = html;
return (0, eval)(buildDumpHtmlScript(selector, depth)) as string;
}
describe('buildDumpHtmlScript — value 属性に残った中身', () => {
it('パスワード欄の value 属性を伏せる', () => {
const out = dump('<input id="pw" type="password" name="password" value="hunter2-topsecret">');
expect(out).not.toContain('hunter2-topsecret');
expect(out).toContain('value="[masked]"');
});
it('ふつうの欄の value 属性はそのまま残す(伏せ過ぎの回帰)', () => {
const out = dump('<input id="u" type="text" name="username" value="alice-visible">');
expect(out).toContain('value="alice-visible"');
expect(out).not.toContain('[masked]');
});
it('選んだ要素そのものが入力欄でも伏せる(根は querySelectorAll に出ない)', () => {
const out = dump(
'<input id="pw" type="password" name="password" value="hunter2-topsecret">',
'#pw',
);
expect(out).not.toContain('hunter2-topsecret');
expect(out).toContain('value="[masked]"');
});
it('名札だけが手掛かりの欄も伏せる(「表示」で伏せ字を解いた欄)', () => {
const out = dump('<input id=":r3:" type="text" aria-label="パスワード" value="hunter2-topsecret">');
expect(out).not.toContain('hunter2-topsecret');
expect(out).toContain('value="[masked]"');
});
it('`<label for>` が付いただけの欄も伏せる', () => {
const out = dump(
'<label for=":r3:">パスワード</label><input id=":r3:" type="text" value="hunter2-topsecret">',
);
expect(out).not.toContain('hunter2-topsecret');
expect(out).toContain('value="[masked]"');
});
it('名札が取得範囲の外にあっても伏せる(複製の中だけを探さないこと)', () => {
// 取るのは `#field` だけ。名札はその外側にあるので、複製を作ってから
// 名札を探す実装では辿れず、パスワードが生の HTML に残る。
const out = dump(
'<label for=":r3:">パスワード</label>' +
'<div id="field"><input id=":r3:" type="text" value="hunter2-topsecret"></div>',
'#field',
);
expect(out).not.toContain('hunter2-topsecret');
expect(out).toContain('value="[masked]"');
});
it('包んでいる `<label>` が取得範囲の外でも伏せる', () => {
// 取るのは `#field` だけ。複製は文書から切り離されるので、複製側で
// 祖先を辿ると `<label>` に届かず、パスワードが生の HTML に残る。
const out = dump(
'<label>パスワード<div id="field">' +
'<input id=":r7:" type="text" value="hunter2-topsecret"></div></label>',
'#field',
);
expect(out).not.toContain('hunter2-topsecret');
expect(out).toContain('value="[masked]"');
});
it('入れ子の `<label>` に包まれた欄も伏せる', () => {
const out = dump('<label>パスワード<input id=":r4:" type="text" value="hunter2-topsecret"></label>');
expect(out).not.toContain('hunter2-topsecret');
expect(out).toContain('value="[masked]"');
});
it('aria-labelledby で名札を指している欄も伏せる', () => {
const out = dump(
'<span id="lbl">パスワード</span>' +
'<input id=":r5:" type="text" aria-labelledby="lbl" value="hunter2-topsecret">',
);
expect(out).not.toContain('hunter2-topsecret');
expect(out).toContain('value="[masked]"');
});
it('離れた名札がふつうの語なら伏せない(伏せ過ぎの回帰)', () => {
const out = dump(
'<label for=":r6:">Shipping address</label><input id=":r6:" type="text" value="tokyo-visible">',
);
expect(out).toContain('value="tokyo-visible"');
expect(out).not.toContain('[masked]');
});
it('autocomplete=one-time-code の欄も伏せる', () => {
const out = dump('<input id="c" type="text" autocomplete="one-time-code" value="482913">');
expect(out).not.toContain('482913');
expect(out).toContain('value="[masked]"');
});
it('textarea は中身(要素のテキスト)も伏せる', () => {
const out = dump('<textarea name="secretNote">hunter2-topsecret</textarea>');
expect(out).not.toContain('hunter2-topsecret');
expect(out).toContain('[masked]');
});
it('伏せる欄とふつうの欄が並んでいても、伏せるのは片方だけ', () => {
const out = dump(
'<form><input name="username" value="alice-visible">' +
'<input name="password" type="password" value="hunter2-topsecret"></form>',
);
expect(out).toContain('value="alice-visible"');
expect(out).toContain('value="[masked]"');
expect(out).not.toContain('hunter2-topsecret');
});
it('複製で印の数がずれたら、印の無い欄まで含めて全部伏せる', () => {
// 複製したときに中身が増える独自要素を置く。`cloneNode(true)` は複製した
// 独自要素をその場で昇格させるので、構築子がもう一度走って複製側だけ欄が
// 増える(issue #1236 レビュー 3 巡目の原因そのもの)。
//
// ここでは構築子が「文書の中の欄を複製して自分に足す」形にしてある。
// 印は複製に乗って運ばれるので、構築子が新しい欄を作るだけなら数はずれ
// ない(印の付いた欄は元も複製も 1 つずつ)。ずれるのは、印を付けた**後**
// に走る構築子が印付きの欄を複製したとき——複製側にだけ印が 1 つ増える。
class DupField extends HTMLElement {
constructor() {
super();
const proto = document.getElementById('proto');
if (proto) this.appendChild(proto.cloneNode(true));
}
}
if (!customElements.get('dup-field')) customElements.define('dup-field', DupField);
const out = dump(
'<div id="field">' +
'<input id="proto" type="password" name="password" value="hunter2-topsecret">' +
'<dup-field></dup-field>' +
'<input name="username" value="alice-visible">' +
'</div>',
'#field',
5,
);
// 数が合わない=どれが秘密か分からない。通す側ではなく全部伏せる側へ倒す。
// 印の付いていなかった利用者名の欄まで伏せられていることが要(合っている
// ときは「伏せるのは片方だけ」で、この欄は素通しになる)。
expect(out).not.toContain('hunter2-topsecret');
expect(out).not.toContain('alice-visible');
expect(out).toContain('value="[masked]"');
// 印は書き出しに残さない。
expect(out).not.toContain('data-ao-mask');
});
it('切り詰めや除去はこれまでどおり効く', () => {
const out = dump('<div><script>var a=1</script><p><b><i>deep</i></b></p></div>', 'div', 1);
expect(out).not.toContain('var a=1');
expect(out).toContain('truncated');
});
});