qa-testing について
リリース前後にページ、機能、サイトを確認するための、技術スタックに依存しないQAガイドです。ブラウザーのコンソール用スニペットを使い、スモークテストから包括的なリリース確認まで3段階で実施します。
主な機能
- 013段階のQAチェック
- 02ブラウザーコンソール用スニペット
- 03メタデータとページ構造の確認
- 04基本的なアクセシビリティとレスポンシブ確認
- 05クロスブラウザーとリリース確認
- 06Markdown監査レポートのテンプレート
活用シーン
- 01デプロイ後のスモークテスト
- 02新しいページや機能の確認
- 03大型リリース前のサイト検証
- 04ページに関する報告の調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ rampstackco/ claude-skills --skill qa-testingインストール後、Claude Code で/qa-testingを入力して読み込まれたか確認します。
同梱ファイルREADME.mdreferences/qa-report-template.md
詳細
QA テスト
ページ、機能、またはサイトが正常に動作していることを確認してから、リリース済みと判断してください。技術スタックを問いません。素早く確認できるよう、コンソールスニペットを使います。
このスキルは、WCAGをより深く掘り下げる accessibility-audit や、Core Web Vitalsをより深く掘り下げる performance-optimization よりも高速です。一般的なQAにはこのスキルを使用してください。詳細な監査には、それぞれの専門スキルを使用してください。
使用するタイミング
- デプロイのたび(スモークテスト)
- 新しいページや機能の公開後(標準監査)
- メジャーリリースの前(完全なリリースマトリックス)
- 「何かがおかしいようだ」という報告の調査
- サイトやセクションの公開前確認
使用しないタイミング
- アクセシビリティ準拠の詳細な作業(
accessibility-auditを使用) - パフォーマンスの詳細な調査(
performance-optimizationを使用) - コードレビューやデバッグ(
code-review-webを使用) - サイトの初期設定やテクニカルSEOのベースライン確認(
seo-technicalを使用)
必要な入力
- テスト対象のページURLまたはサイト
- 必要なQAのティア(スモーク、標準、または完全)
- ブラウザーの開発者ツールへのアクセス
- 標準ティア以外に確認すべき具体的な懸念事項
フレームワーク:3つのティア
QAの範囲は重要度に応じて調整します。状況に合ったティアを選んでください。
| ティア | 実施タイミング | 所要時間 | 対象範囲 |
|---|---|---|---|
| スモーク | デプロイのたび | 2分 | 重要なシグナルのみ |
| 標準 | 新しいページや機能 | 10分 | ページ上の基本事項、アクセシビリティ、構造 |
| 完全 | メジャーリリース、公開前 | 30分以上 | すべての観点を包括的に確認 |
ティア1:スモークテスト
「デプロイで明らかな問題が起きていないか」を2分で確認します。デプロイのたびに実施してください。
コンソールスニペット(ブラウザーの開発者ツールに貼り付けます):
const smoke = {
title: document.title,
titleLen: document.title.length,
canonical: document.querySelector('link[rel="canonical"]')?.href,
h1Count: document.querySelectorAll('h1').length,
missingAlts: [...document.querySelectorAll('img')].filter(i => !i.hasAttribute('alt')).length,
schema: [...document.querySelectorAll('script[type="application/ld+json"]')]
.map(s => { try { return JSON.parse(s.innerText)['@type'] } catch(e) { return 'invalid' } }),
brokenImages: [...document.querySelectorAll('img')].filter(i => !i.complete || i.naturalWidth === 0).length,
};
console.log(JSON.stringify(smoke, null, 2));
合格基準:
- タイトルが存在し、30〜60文字である
- canonical が本番ドメインを指している(ステージングやプレビューのURLは不可)
- H1が1つだけ存在する
alt属性のない画像が0件である。装飾画像の空のalt=""は正しいマークアップとして合格します。失敗となるのは、属性自体がない場合のみです。- 壊れた画像が0件である
- すべてのスキーマブロックを解析できる(スニペットの出力に
invalidの項目がない)。そのページに適切な種類のスキーマかどうかは、Rich Results Testを使って完全ティアで確認します。
いずれかの基準を満たさない場合は、スモークテストの問題を修正するまで、より詳細なテストに進まないでください。
ティア2:標準ページ監査
新しいページや機能を対象とした10分間の監査です。ページ上の基本事項に加え、アクセシビリティと構造を確認します。
コンソールスニペット:
const audit = {
title: document.title,
titleLen: document.title.length,
canonical: document.querySelector('link[rel="canonical"]')?.href,
metaDesc: document.querySelector('meta[name="description"]')?.content,
metaDescLen: document.querySelector('meta[name="description"]')?.content?.length,
ogImage: document.querySelector('meta[property="og:image"]')?.content,
ogTitle: document.querySelector('meta[property="og:title"]')?.content,
twitterCard: document.querySelector('meta[name="twitter:card"]')?.content,
h1Count: document.querySelectorAll('h1').length,
h1Text: document.querySelector('h1')?.innerText,
h2Count: document.querySelectorAll('h2').length,
h2s: [...document.querySelectorAll('h2')].map(h => h.innerText.trim().slice(0, 60)),
totalImages: document.querySelectorAll('img').length,
missingAlts: [...document.querySelectorAll('img')].filter(i => !i.hasAttribute('alt')).length,
brokenImages: [...document.querySelectorAll('img')].filter(i => !i.complete || i.naturalWidth === 0).length,
externalLinksWithoutNoopener: [...document.querySelectorAll('a[target="_blank"]')]
.filter(a => !a.rel?.includes('noopener')).length,
schema: [...document.querySelectorAll('script[type="application/ld+json"]')]
.map(s => {
try {
const d = JSON.parse(s.innerText);
return d['@graph'] ? d['@graph'].map(x => x['@type']) : d['@type'];
} catch(e) { return 'invalid' }
}),
hasSkipLink: [...document.querySelectorAll('a[href^="#"]')].slice(0, 3)
.some(a => /skip/i.test(a.textContent) && !!document.getElementById(a.getAttribute('href').slice(1))),
pageLanguage: document.documentElement.lang || 'NOT SET',
hasFavicon: !!document.querySelector('link[rel*="icon"]'),
};
console.log(JSON.stringify(audit, null, 2));
合格基準(スモークテストに加えて):
- メタディスクリプションが120〜160文字である
- og:image、og:title、twitter:card が存在する
- H2が存在し、内容がわかりやすい
target="_blank"を持つすべての外部リンクにrel="noopener"が指定されている- ページの言語が宣言されている(
<html>のlang属性) - ファビコンが存在する
ティア 3: フルリリースマトリクス
ローンチ前 30 分のチェックです。すべての観点を網羅します。
| 観点 | 合格基準 |
|---|---|
| スモークおよび標準 | すべて合格 |
| アクセシビリティ (基本) | ブラウザ監査ツール (例: Lighthouse) を実行し、スコア 90 以上 |
| パフォーマンス (基本) | レポートテンプレートのパフォーマンスチェックリストのすべてのしきい値 (INP を含む) |
| モバイル対応 | レポートテンプレートのレスポンシブチェックリストのすべてのビューポート |
| クロスブラウザ | Chrome、Safari、Firefox (関連するオーディエンスがいる場合は Edge も) でテスト済み |
| フォーム | すべてのフォームが正常に送信され、正しく検証される |
| 内部リンク | 壊れた内部リンクがない (ランダムに 20 件サンプリング) |
| 外部リンク | すべて 200 を返す (10 件サンプリング) |
| サイトマップ | 200 を返し、正規 URL のみをリストする |
| robots.txt | 本番クローラーを許可し、該当する場合はステージングをブロックする |
| セキュリティヘッダー | HSTS、X-Frame-Options、X-Content-Type-Options が存在する |
| HTTPS | すべてのリソースが HTTPS 経由で読み込まれ、混合コンテンツがない |
| 404 処理 | 404 ページが HTTP 404 を返す (ソフト 200 ではない) |
| スキーマ検証 | すべてのスキーマがリッチリザルトテストで検証される |
| アナリティクス | 主要なユーザーアクションでイベントが期待どおりに発火する |
| キャッシュ動作 | ページタイプに適したキャッシュヘッダー |
ヘッダーについては、次を実行します:
fetch(window.location.origin, { method: 'HEAD' })
.then(r => {
const headers = {};
for (const [k, v] of r.headers.entries()) headers[k] = v;
console.log(JSON.stringify(headers, null, 2));
});
探す項目: strict-transport-security、x-frame-options、x-content-type-options。
特定の QA スニペット
画像監査
const imgs = [...document.querySelectorAll('img')].map(i => ({
src: i.src.split('/').pop().split('?')[0].slice(0, 60),
alt: i.hasAttribute('alt') ? (i.alt === '' ? 'DECORATIVE (empty alt)' : i.alt) : 'MISSING',
width: i.naturalWidth,
height: i.naturalHeight,
loaded: i.complete && i.naturalWidth > 0,
}));
console.table(imgs);
console.log({
total: imgs.length,
broken: imgs.filter(i => !i.loaded).length,
noAlt: imgs.filter(i => i.alt === 'MISSING').length,
decorative: imgs.filter(i => i.alt.startsWith('DECORATIVE')).length,
});
見出し階層チェック
const headings = [...document.querySelectorAll('h1, h2, h3, h4, h5, h6')].map(h => ({
level: parseInt(h.tagName[1]),
text: h.innerText.trim().slice(0, 80),
}));
console.table(headings);
// Check for skipped levels
const levels = headings.map(h => h.level);
let skipped = false;
for (let i = 1; i < levels.length; i++) {
if (levels[i] > levels[i-1] + 1) {
console.warn(`Skipped from H${levels[i-1]} to H${levels[i]}: "${headings[i].text}"`);
skipped = true;
}
}
if (!skipped) console.log('No skipped heading levels');
コントラストのスポットチェック
function contrast(bg, fg) {
function lum(hex) {
return [hex.slice(1,3), hex.slice(3,5), hex.slice(5,7)]
.map(h => parseInt(h, 16) / 255)
.map(v => v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4))
.reduce((a, v, i) => a + v * [0.2126, 0.7152, 0.0722][i], 0);
}
const [l1, l2] = [lum(bg), lum(fg)];
const r = ((Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05)).toFixed(2);
return r + ':1 ' + (parseFloat(r) >= 4.5 ? 'PASS body' : parseFloat(r) >= 3 ? 'PASS large only' : 'FAIL');
}
// Examples
contrast('#FFFFFF', '#4B5563'); // body color check
contrast('#FFFFFF', '#9CA3AF'); // verify gray choices
フォーム監査 (フォームごと)
[...document.querySelectorAll('form')].forEach((form, i) => {
const fields = [...form.querySelectorAll('input, select, textarea')].map(field => ({
type: field.type || field.tagName.toLowerCase(),
name: field.name,
hasLabel: !!form.querySelector(`label[for="${field.id}"]`) || !!field.closest('label'),
required: field.required,
}));
console.log(`Form ${i + 1}:`);
console.table(fields);
});
外部リンク監査
const externalLinks = [...document.querySelectorAll('a[href^="http"]')]
.filter(a => !a.href.includes(window.location.host));
const issues = externalLinks.filter(a =>
a.target === '_blank' && (!a.rel?.includes('noopener') || !a.rel?.includes('noreferrer'))
);
if (issues.length) {
console.warn(`${issues.length} external links missing noopener/noreferrer:`);
issues.forEach(a => console.warn(a.href));
} else {
console.log(`All ${externalLinks.length} external links properly attributed`);
}
ワークフロー
- ティアを選びます。 ルーチンデプロイにはスモーク。新しい作業には標準。リリースにはフル。
- スニペットを実行します。 適切なコンソールスニペットを貼り付け、出力を確認します。
- 失敗を記録します。 各失敗は、出荷前に修正するか、既知の問題として起票します。
- 標準ティアの場合、次を追加します: 375px、768px、1440px でのビジュアルレビュー。標準は意図的に 1024px をスキップします。フルティアがテンプレートのレスポンシブチェックリストの残りとともにそれを拾います。主要なユーザーフローをテストします。
- フルティアの場合、次を追加します: クロスブラウザテスト、Lighthouse 監査、スキーマ検証、セキュリティヘッダー、404 処理。
- 文書化します。 完全な監査には
references/qa-report-template.mdのテンプレートを使用します。
失敗パターン
- 「小さな」デプロイでスモークテストをスキップする。 本番障害の半分は、「安全に見えた」デプロイから始まります。
- スニペットを実行するが、出力を読まない。 コンソールスニペットはツールです。判断とは、それが返すものを読むことです。
- ビジュアルのみの QA。 ページを目視で確認するだけでは、alt テキストの欠落、壊れたスキーマ、canonical の欠落を見逃します。常にスニペットを実行してください。
- 単一ブラウザでのテスト。 モバイル Safari と Chrome は、驚くほど異なります。少なくとも Chrome と Safari でテストしてください。
- モバイル QA なし。 375px のビューポートは、ほとんどのユーザーがいる場所です。そこでテストするか、後で痛い目を見るかです。
- 修正なしの合否判定。 失敗した QA は、修正または既知の問題チケットを生み出さなければなりません。修正されないまま出荷される失敗した QA は、プロセスの見せかけです。
出力形式
スモークテストの場合: コンソール出力がレポートです。
標準およびフル監査の場合: qa-report-[date].md の Markdown レポート。テンプレートは references/qa-report-template.md を使用します。
必要なデータが利用できない場合
このスキルの出力は、それ自体では生成できないデータ、測定値、またはツールの結果に依存します。必要な入力、ツール、またはデータソースが利用できない、または検証できない場合、認められる出力は、ギャップを明記した成果物です: 何が必要だったか、実際に何が得られたかまたは検証されたか、出力のどの部分が影響を受けるか。成果物を完成させるために必要な数値を捏造、推定、または補間することは決して認められません。明記されたギャップは完全な回答です。
参照ファイル
references/qa-report-template.md- 標準およびフル監査用の Markdown レポートテンプレート。