{"id":379,"date":"2026-07-30T15:19:15","date_gmt":"2026-07-30T07:19:15","guid":{"rendered":"https:\/\/numsimlab.com\/?p=379"},"modified":"2026-07-30T15:19:15","modified_gmt":"2026-07-30T07:19:15","slug":"playwright","status":"publish","type":"post","link":"https:\/\/numsimlab.com\/?p=379","title":{"rendered":"Playwright"},"content":{"rendered":"\n<!-- Generated by Trae Work -->\n<!DOCTYPE html>\n<html lang=\"zh\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Playwright \u4ecb\u7ecd | Playwright Introduction<\/title>\n  <meta name=\"description\" content=\"Playwright \u6d4f\u89c8\u5668\u81ea\u52a8\u5316\u6d4b\u8bd5\u6846\u67b6\u5b8c\u6574\u4ecb\u7ecd\">\n\n  <style>\n    \/* === CSS Variables & Theme System === *\/\n    :root {\n      --bg: #f8f9fb;\n      --bg2: #ffffff;\n      --bg2-rgb: 255, 255, 255;\n      --ink: #1e293b;\n      --muted: #64748b;\n      --rule: #e2e8f0;\n      --accent: #2d9cdb;\n      --accent2: #7b61ff;\n      --accent-light: rgba(45, 156, 219, 0.08);\n      --accent2-light: rgba(123, 97, 255, 0.08);\n      --shadow: rgba(30, 41, 59, 0.06);\n      --shadow-lg: rgba(30, 41, 59, 0.1);\n      --code-bg: #f1f5f9;\n      --code-ink: #334155;\n      --code-header-bg: rgba(0, 0, 0, 0.03);\n      --code-inline-bg: rgba(0, 0, 0, 0.05);\n      --success: #10b981;\n      --warning: #f59e0b;\n      --font: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Microsoft YaHei\", \"PingFang SC\", \"Noto Sans CJK SC\", sans-serif;\n      --font-mono: \"JetBrains Mono\", \"Fira Code\", \"Cascadia Code\", Consolas, \"Courier New\", monospace;\n      --max: 960px;\n      --radius: 12px;\n      --radius-sm: 8px;\n    }\n\n    [data-theme=\"dark\"] {\n      --bg: #0f172a;\n      --bg2: #1e293b;\n      --bg2-rgb: 30, 41, 59;\n      --ink: #f1f5f9;\n      --muted: #94a3b8;\n      --rule: #334155;\n      --accent: #38bdf8;\n      --accent2: #a78bfa;\n      --accent-light: rgba(56, 189, 248, 0.1);\n      --accent2-light: rgba(167, 139, 250, 0.1);\n      --shadow: rgba(0, 0, 0, 0.25);\n      --shadow-lg: rgba(0, 0, 0, 0.35);\n      --code-bg: #1e293b;\n      --code-ink: #e2e8f0;\n      --code-header-bg: rgba(255, 255, 255, 0.03);\n      --code-inline-bg: rgba(255, 255, 255, 0.06);\n    }\n\n    \/* === Base === *\/\n    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n    html { font-size: 16px; scroll-behavior: smooth; }\n    body {\n      font-family: var(--font);\n      color: var(--ink);\n      background: var(--bg);\n      line-height: 1.75;\n      transition: background 0.3s ease, color 0.3s ease;\n      -webkit-font-smoothing: antialiased;\n    }\n\n    \/* === Typography === *\/\n    h1, h2, h3, h4 { font-weight: 700; line-height: 1.3; letter-spacing: -0.02em; }\n    h1 { font-size: clamp(2rem, 5vw, 3rem); }\n    h2 { font-size: clamp(1.5rem, 3vw, 2rem); margin-top: 3rem; margin-bottom: 1rem; }\n    h3 { font-size: clamp(1.1rem, 2vw, 1.35rem); margin-top: 2rem; margin-bottom: 0.75rem; }\n    p { margin-bottom: 1rem; color: var(--muted); }\n    strong { color: var(--ink); font-weight: 600; }\n    a { color: var(--accent); text-decoration: none; }\n    a:hover { text-decoration: underline; }\n    code { font-family: var(--font-mono); }\n    p > code, li > code {\n      background: var(--code-inline-bg); padding: 0.15em 0.4em;\n      border-radius: 4px; font-size: 0.875em; color: var(--ink);\n    }\n\n    \/* === Layout === *\/\n    .container { max-width: var(--max); margin: 0 auto; padding: 0 1.5rem; }\n    section { margin-bottom: 4rem; scroll-margin-top: 70px; }\n\n    \/* === Reading Progress Bar === *\/\n    .progress-bar {\n      position: fixed; top: 0; left: 0; height: 3px; width: 0;\n      background: linear-gradient(90deg, var(--accent), var(--accent2));\n      z-index: 200; transition: width 0.1s ease;\n    }\n\n    \/* === Navigation === *\/\n    nav {\n      position: sticky; top: 0; z-index: 100;\n      background: rgba(var(--bg2-rgb), 0.85);\n      backdrop-filter: blur(12px);\n      -webkit-backdrop-filter: blur(12px);\n      border-bottom: 1px solid var(--rule);\n      transition: background 0.3s ease, box-shadow 0.3s ease;\n    }\n    nav.scrolled { box-shadow: 0 2px 12px var(--shadow); }\n    .nav-inner {\n      max-width: var(--max); margin: 0 auto; padding: 0.75rem 1.5rem;\n      display: flex; justify-content: space-between; align-items: center; gap: 1rem;\n    }\n    .nav-brand {\n      font-weight: 700; font-size: 1.25rem; color: var(--ink);\n      display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0;\n    }\n    .nav-brand svg { width: 28px; height: 28px; }\n    .nav-links {\n      display: flex; gap: 0.25rem; flex: 1; justify-content: center;\n      overflow-x: auto; scrollbar-width: none;\n    }\n    .nav-links::-webkit-scrollbar { display: none; }\n    .nav-link {\n      padding: 0.375rem 0.75rem; border-radius: var(--radius-sm);\n      color: var(--muted); font-size: 0.8125rem; font-weight: 500;\n      white-space: nowrap; cursor: pointer; transition: all 0.2s ease;\n      border: none; background: none; font-family: var(--font);\n    }\n    .nav-link:hover { color: var(--ink); background: var(--accent-light); }\n    .nav-link.active { color: var(--accent); background: var(--accent-light); font-weight: 600; }\n    .nav-actions { display: flex; gap: 0.5rem; align-items: center; flex-shrink: 0; }\n    .btn-icon {\n      width: 36px; height: 36px; border-radius: var(--radius-sm);\n      border: 1px solid var(--rule); background: var(--bg2); color: var(--ink);\n      cursor: pointer; display: flex; align-items: center; justify-content: center;\n      transition: all 0.2s ease; font-size: 0.875rem;\n    }\n    .btn-icon:hover { background: var(--accent-light); border-color: var(--accent); }\n    .btn-icon.active { background: var(--accent); color: #fff; border-color: var(--accent); }\n\n    \/* Language toggle: \u4e2d\/EN *\/\n    .lang-toggle {\n      width: auto; padding: 0 0.625rem; gap: 0;\n      font-size: 0.8125rem; font-family: var(--font);\n      letter-spacing: 0.02em;\n    }\n    .lang-toggle .lang-zh,\n    .lang-toggle .lang-en {\n      transition: opacity 0.2s ease, font-weight 0.2s ease;\n    }\n    .lang-toggle .lang-sep {\n      margin: 0 0.15rem; opacity: 0.3; font-weight: 400;\n    }\n\n    \/* === Hero === *\/\n    .hero {\n      padding: 4rem 0 3rem;\n      text-align: center;\n      position: relative;\n      overflow: hidden;\n    }\n    .hero::before {\n      content: '';\n      position: absolute; top: -50%; left: -50%; width: 200%; height: 200%;\n      background:\n        radial-gradient(circle at 30% 30%, rgba(45, 156, 219, 0.08) 0%, transparent 50%),\n        radial-gradient(circle at 70% 70%, rgba(123, 97, 255, 0.06) 0%, transparent 50%);\n      pointer-events: none;\n    }\n    .hero-badge {\n      display: inline-flex; align-items: center; gap: 0.5rem;\n      padding: 0.375rem 1rem; border-radius: 9999px;\n      background: var(--accent-light); color: var(--accent);\n      font-size: 0.875rem; font-weight: 500; margin-bottom: 1.5rem;\n      position: relative;\n    }\n    .hero h1 {\n      background: linear-gradient(135deg, var(--ink) 0%, var(--accent) 100%);\n      -webkit-background-clip: text; -webkit-text-fill-color: transparent;\n      background-clip: text; margin-bottom: 1rem; position: relative;\n    }\n    [data-theme=\"dark\"] .hero h1 {\n      background: linear-gradient(135deg, #fff 0%, var(--accent) 100%);\n      -webkit-background-clip: text; background-clip: text;\n      -webkit-text-fill-color: transparent;\n    }\n    .hero-subtitle {\n      font-size: clamp(1rem, 2vw, 1.25rem); color: var(--muted);\n      max-width: 600px; margin: 0 auto 2rem; position: relative;\n    }\n    .hero-actions { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; position: relative; }\n    .btn {\n      padding: 0.75rem 1.5rem; border-radius: var(--radius-sm); font-weight: 600;\n      cursor: pointer; transition: all 0.2s ease; border: none; font-size: 1rem;\n      display: inline-flex; align-items: center; gap: 0.5rem;\n    }\n    .btn-primary {\n      background: linear-gradient(135deg, var(--accent), var(--accent2));\n      color: #fff; box-shadow: 0 4px 14px rgba(45, 156, 219, 0.3);\n    }\n    .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(45, 156, 219, 0.4); }\n    .btn-secondary {\n      background: var(--bg2); color: var(--ink); border: 1px solid var(--rule);\n    }\n    .btn-secondary:hover { background: var(--accent-light); border-color: var(--accent); }\n\n    \/* === Cards === *\/\n    .card-grid {\n      display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));\n      gap: 1.5rem; margin-top: 1.5rem;\n    }\n    .card {\n      background: var(--bg2);\n      border: 1px solid var(--rule);\n      border-radius: var(--radius);\n      padding: 1.5rem;\n      transition: all 0.3s ease;\n      position: relative;\n      overflow: hidden;\n    }\n    .card::before {\n      content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px;\n      background: linear-gradient(90deg, var(--accent), var(--accent2));\n      opacity: 0; transition: opacity 0.3s ease;\n    }\n    .card:hover { transform: translateY(-4px); box-shadow: 0 12px 40px var(--shadow-lg); }\n    .card:hover::before { opacity: 1; }\n    .card-icon {\n      width: 40px; height: 40px; border-radius: var(--radius-sm);\n      background: var(--accent-light); color: var(--accent);\n      display: flex; align-items: center; justify-content: center;\n      margin-bottom: 1rem; font-size: 1.25rem;\n    }\n    .card h3 { margin-top: 0; margin-bottom: 0.5rem; font-size: 1.1rem; }\n    .card p { font-size: 0.9375rem; margin-bottom: 0; }\n\n    \/* === Code Blocks === *\/\n    .code-block {\n      background: var(--code-bg);\n      border: 1px solid var(--rule);\n      border-radius: var(--radius);\n      overflow: hidden; margin: 1.5rem 0;\n    }\n    .code-header {\n      display: flex; justify-content: space-between; align-items: center;\n      padding: 0.75rem 1rem; background: var(--code-header-bg);\n      border-bottom: 1px solid var(--rule);\n    }\n    .code-tabs { display: flex; gap: 0.25rem; }\n    .code-tab {\n      padding: 0.375rem 0.875rem; border-radius: var(--radius-sm);\n      border: none; background: transparent; color: var(--muted);\n      cursor: pointer; font-size: 0.875rem; font-weight: 500;\n      transition: all 0.2s ease;\n    }\n    .code-tab:hover { color: var(--ink); }\n    .code-tab.active { background: var(--accent); color: #fff; }\n    .code-body { position: relative; }\n    .code-pane { display: none; overflow-x: auto; }\n    .code-pane.active { display: flex; }\n    .code-pane pre {\n      font-family: var(--font-mono); font-size: 0.875rem;\n      line-height: 1.7; color: var(--code-ink); margin: 0;\n      padding: 1rem 1rem 1rem 0; flex: 1;\n    }\n    .line-numbers {\n      display: flex; flex-direction: column; padding: 1rem 0.75rem 1rem 1rem;\n      text-align: right; user-select: none; flex-shrink: 0;\n      font-family: var(--font-mono); font-size: 0.875rem; line-height: 1.7;\n      color: var(--muted); opacity: 0.4;\n      border-right: 1px solid var(--rule);\n    }\n    .copy-btn {\n      padding: 0.375rem 0.75rem; border-radius: var(--radius-sm);\n      border: 1px solid var(--rule); background: var(--bg2);\n      color: var(--muted); cursor: pointer; font-size: 0.8rem;\n      transition: all 0.2s ease; display: flex; align-items: center; gap: 0.3rem;\n    }\n    .copy-btn:hover { background: var(--accent-light); color: var(--accent); border-color: var(--accent); }\n\n    \/* Syntax highlighting *\/\n    .keyword { color: #c678dd; }\n    .string { color: #98c379; }\n    .comment { color: #5c6370; font-style: italic; }\n    .function { color: #61afef; }\n    .number { color: #d19a66; }\n    [data-theme=\"dark\"] .keyword { color: #c792ea; }\n    [data-theme=\"dark\"] .string { color: #c3e88d; }\n    [data-theme=\"dark\"] .comment { color: #676e95; }\n    [data-theme=\"dark\"] .function { color: #82aaff; }\n    [data-theme=\"dark\"] .number { color: #f78c6c; }\n\n    \/* === Tables === *\/\n    .table-wrap {\n      overflow-x: auto; overflow-y: auto; max-height: 600px;\n      margin: 1.5rem 0; border-radius: var(--radius);\n      border: 1px solid var(--rule);\n    }\n    table { width: 100%; border-collapse: collapse; font-size: 0.9375rem; }\n    thead { background: var(--accent-light); position: sticky; top: 0; z-index: 1; }\n    th { padding: 0.875rem 1rem; text-align: left; font-weight: 600; color: var(--ink); border-bottom: 2px solid var(--accent); white-space: nowrap; }\n    td { padding: 0.875rem 1rem; border-bottom: 1px solid var(--rule); color: var(--muted); }\n    tr:last-child td { border-bottom: none; }\n    tr:hover td { background: var(--accent-light); }\n    .table-caption {\n      text-align: center; font-size: 0.875rem; color: var(--muted);\n      margin-top: 0.75rem; font-weight: 500;\n    }\n\n    \/* === Diagram \/ Figure === *\/\n    figure { margin: 2rem 0; }\n    figcaption {\n      text-align: center; font-size: 0.875rem; color: var(--muted);\n      margin-top: 0.75rem; font-weight: 500;\n    }\n    .arch-diagram {\n      display: flex; flex-direction: column; align-items: center; gap: 0;\n      padding: 2.5rem 1.5rem; background: var(--bg2); border-radius: var(--radius);\n      border: 1px solid var(--rule); position: relative;\n    }\n    .arch-layer {\n      display: flex; gap: 1rem; flex-wrap: wrap; justify-content: center;\n      position: relative; z-index: 1;\n    }\n    .arch-box {\n      padding: 0.75rem 1.5rem; border-radius: var(--radius-sm);\n      background: var(--accent-light); color: var(--accent);\n      font-weight: 600; font-size: 0.9375rem; border: 1px solid rgba(45, 156, 219, 0.2);\n      min-width: 140px; text-align: center;\n      transition: transform 0.2s ease, box-shadow 0.2s ease;\n    }\n    .arch-box:hover { transform: scale(1.05); box-shadow: 0 4px 16px var(--shadow); }\n    .arch-box.secondary { background: var(--accent2-light); color: var(--accent2); border-color: rgba(123, 97, 255, 0.2); }\n    .arch-box.mono { background: var(--code-bg); color: var(--code-ink); border-color: var(--rule); }\n    .arch-connector {\n      width: 2px; height: 28px;\n      background: linear-gradient(180deg, var(--rule), var(--accent));\n      position: relative; margin: 0;\n    }\n    .arch-connector::after {\n      content: ''; position: absolute; bottom: -1px; left: 50%;\n      transform: translateX(-50%);\n      width: 0; height: 0;\n      border-left: 5px solid transparent; border-right: 5px solid transparent;\n      border-top: 6px solid var(--accent);\n    }\n\n    \/* === Language Content Toggle === *\/\n    [data-lang=\"en\"] .zh-only { display: none; }\n    [data-lang=\"zh\"] .en-only { display: none; }\n\n    \/* === Section Headers === *\/\n    .section-header {\n      display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1.5rem;\n    }\n    .section-number {\n      width: 32px; height: 32px; border-radius: 50%;\n      background: linear-gradient(135deg, var(--accent), var(--accent2));\n      color: #fff; display: flex; align-items: center; justify-content: center;\n      font-weight: 700; font-size: 0.875rem; flex-shrink: 0;\n    }\n\n    \/* === Scroll Animations === *\/\n    \/* \u9ed8\u8ba4\u53ef\u89c1\uff1a\u786e\u4fdd JS \u672a\u6267\u884c\u6216\u4e2d\u65ad\u65f6\u5185\u5bb9\u4e0d\u4f1a\u7a7a\u767d\uff1b\u4ec5\u5f53 html \u62e5\u6709 js \u7c7b\u65f6\u624d\u542f\u7528\u9690\u85cf\u52a8\u753b *\/\n    .fade-in {\n      opacity: 1; transform: none;\n      transition: opacity 0.6s ease, transform 0.6s ease;\n    }\n    html.js .fade-in {\n      opacity: 0; transform: translateY(20px);\n    }\n    html.js .fade-in.visible { opacity: 1; transform: translateY(0); }\n\n    \/* === Back to Top === *\/\n    .back-to-top {\n      position: fixed; bottom: 2rem; right: 2rem; z-index: 90;\n      width: 44px; height: 44px; border-radius: 50%;\n      background: var(--bg2); border: 1px solid var(--rule); color: var(--ink);\n      cursor: pointer; display: flex; align-items: center; justify-content: center;\n      opacity: 0; pointer-events: none;\n      transition: all 0.3s ease;\n      box-shadow: 0 4px 14px var(--shadow);\n    }\n    .back-to-top.visible { opacity: 1; pointer-events: auto; }\n    .back-to-top:hover {\n      background: var(--accent); color: #fff; border-color: var(--accent);\n      transform: translateY(-2px); box-shadow: 0 6px 20px var(--shadow-lg);\n    }\n\n    \/* === Quick Start Card === *\/\n    .quick-start {\n      margin-top: 1.5rem; padding: 1.5rem;\n      background: linear-gradient(135deg, var(--accent-light), var(--accent2-light));\n      border-radius: var(--radius); border: 1px solid var(--rule);\n    }\n    .quick-start pre {\n      background: var(--code-bg); padding: 1rem; border-radius: var(--radius-sm);\n      margin-bottom: 0; font-size: 0.875rem; line-height: 1.8;\n      color: var(--code-ink); overflow-x: auto;\n    }\n\n    \/* === Footer === *\/\n    footer {\n      border-top: 1px solid var(--rule); padding: 2rem 0; margin-top: 4rem;\n      text-align: center; color: var(--muted); font-size: 0.875rem;\n    }\n\n    \/* === Responsive === *\/\n    @media (max-width: 768px) {\n      .nav-links { display: none; }\n    }\n    @media (max-width: 640px) {\n      .hero { padding: 2.5rem 0 2rem; }\n      .card-grid { grid-template-columns: 1fr; }\n      .arch-layer { flex-direction: column; align-items: center; }\n      .arch-connector { width: 28px; height: 2px; background: linear-gradient(90deg, var(--rule), var(--accent)); }\n      .arch-connector::after { bottom: 50%; left: auto; right: -1px; top: 50%; transform: translateY(50%); border-top: 5px solid transparent; border-bottom: 5px solid transparent; border-left: 6px solid var(--accent); }\n      .nav-inner { padding: 0.75rem 1rem; }\n      .table-wrap { border-radius: var(--radius-sm); }\n      th, td { padding: 0.625rem 0.75rem; font-size: 0.875rem; }\n      .back-to-top { bottom: 1rem; right: 1rem; }\n      .line-numbers { display: none; }\n      .code-pane.active { display: block; }\n      .code-pane pre { padding: 1rem; }\n    }\n\n    \/* === Print === *\/\n    @media print {\n      .progress-bar, nav, .hero-actions, .btn-icon, .back-to-top, .nav-links { display: none; }\n      body { background: #fff; color: #000; }\n      .card { break-inside: avoid; box-shadow: none; border: 1px solid #ddd; }\n      .fade-in { opacity: 1; transform: none; }\n    }\n  <\/style>\n<\/head>\n<body data-theme=\"light\" data-lang=\"zh\">\n\n  <!-- Reading Progress Bar -->\n  <div class=\"progress-bar\" id=\"progressBar\"><\/div>\n\n  <!-- Navigation -->\n  <nav id=\"navbar\">\n    <div class=\"nav-inner\">\n      <div class=\"nav-brand\">\n        <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n          <path d=\"M12 2L2 7l10 5 10-5-10-5z\"\/>\n          <path d=\"M2 17l10 5 10-5\"\/>\n          <path d=\"M2 12l10 5 10-5\"\/>\n        <\/svg>\n        <span>Playwright<\/span>\n      <\/div>\n      <div class=\"nav-links\">\n        <button class=\"nav-link\" data-target=\"overview\"><span class=\"zh-only\">\u6982\u8ff0<\/span><span class=\"en-only\">Overview<\/span><\/button>\n        <button class=\"nav-link\" data-target=\"features\"><span class=\"zh-only\">\u7279\u6027<\/span><span class=\"en-only\">Features<\/span><\/button>\n        <button class=\"nav-link\" data-target=\"architecture\"><span class=\"zh-only\">\u67b6\u6784<\/span><span class=\"en-only\">Architecture<\/span><\/button>\n        <button class=\"nav-link\" data-target=\"usage\"><span class=\"zh-only\">\u7528\u6cd5<\/span><span class=\"en-only\">Usage<\/span><\/button>\n        <button class=\"nav-link\" data-target=\"scenarios\"><span class=\"zh-only\">\u573a\u666f<\/span><span class=\"en-only\">Use Cases<\/span><\/button>\n        <button class=\"nav-link\" data-target=\"comparison\"><span class=\"zh-only\">\u5bf9\u6bd4<\/span><span class=\"en-only\">Comparison<\/span><\/button>\n        <button class=\"nav-link\" data-target=\"summary\"><span class=\"zh-only\">\u603b\u7ed3<\/span><span class=\"en-only\">Summary<\/span><\/button>\n      <\/div>\n      <div class=\"nav-actions\">\n        <button class=\"btn-icon lang-toggle\" id=\"langBtn\" title=\"\u5207\u6362\u8bed\u8a00 \/ Switch Language\">\n          <span class=\"lang-zh\" id=\"langZh\">\u4e2d<\/span><span class=\"lang-sep\">\/<\/span><span class=\"lang-en\" id=\"langEn\">EN<\/span>\n        <\/button>\n        <button class=\"btn-icon\" id=\"themeBtn\" title=\"\u5207\u6362\u4e3b\u9898 \/ Toggle Theme\">\n          <svg id=\"themeIcon\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n            <circle cx=\"12\" cy=\"12\" r=\"5\"\/>\n            <path d=\"M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42\"\/>\n          <\/svg>\n        <\/button>\n      <\/div>\n    <\/div>\n  <\/nav>\n\n  <div class=\"container\">\n    <!-- Hero -->\n    <header class=\"hero fade-in\">\n      <div class=\"hero-badge\">\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 2L2 7l10 5 10-5-10-5z\"\/><path d=\"M2 17l10 5 10-5\"\/><path d=\"M2 12l10 5 10-5\"\/><\/svg>\n        <span class=\"zh-only\">Microsoft \u5f00\u6e90\u9879\u76ee<\/span>\n        <span class=\"en-only\">Microsoft Open Source<\/span>\n      <\/div>\n      <h1>\n        <span class=\"zh-only\">Playwright<\/span>\n        <span class=\"en-only\">Playwright<\/span>\n      <\/h1>\n      <p class=\"hero-subtitle zh-only\">\u4e13\u4e3a\u73b0\u4ee3 Web \u5e94\u7528\u8bbe\u8ba1\u7684\u8de8\u6d4f\u89c8\u5668\u81ea\u52a8\u5316\u6d4b\u8bd5\u6846\u67b6<\/p>\n      <p class=\"hero-subtitle en-only\">Cross-browser automation testing framework for modern web apps<\/p>\n      <div class=\"hero-actions\">\n        <a href=\"https:\/\/playwright.dev\" target=\"_blank\" class=\"btn btn-primary zh-only\">\n          <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"\/><polyline points=\"15 3 21 3 21 9\"\/><line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\"\/><\/svg>\n          \u5b98\u65b9\u6587\u6863\n        <\/a>\n        <a href=\"https:\/\/playwright.dev\" target=\"_blank\" class=\"btn btn-primary en-only\">\n          <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"\/><polyline points=\"15 3 21 3 21 9\"\/><line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\"\/><\/svg>\n          Official Docs\n        <\/a>\n        <a href=\"#features\" class=\"btn btn-secondary zh-only\">\u4e86\u89e3\u66f4\u591a<\/a>\n        <a href=\"#features\" class=\"btn btn-secondary en-only\">Learn More<\/a>\n      <\/div>\n    <\/header>\n\n    <main>\n      <!-- Overview -->\n      <section id=\"overview\" class=\"fade-in\">\n        <div class=\"section-header\">\n          <div class=\"section-number\">1<\/div>\n          <h2 class=\"zh-only\">\u6982\u8ff0<\/h2>\n          <h2 class=\"en-only\">Overview<\/h2>\n        <\/div>\n        <p class=\"zh-only\">\n          Playwright \u662f\u7531 Microsoft \u5f00\u53d1\u7684\u5f00\u6e90\u6d4f\u89c8\u5668\u81ea\u52a8\u5316\u6846\u67b6\u3002\u5b83\u901a\u8fc7\u7edf\u4e00\u7684 API \u63a7\u5236 <strong>Chromium<\/strong>\u3001<strong>Firefox<\/strong> \u548c <strong>WebKit<\/strong>\uff0c\u4f7f\u5f00\u53d1\u8005\u80fd\u591f\u7f16\u5199\u4e00\u6b21\u6d4b\u8bd5\u811a\u672c\uff0c\u5373\u53ef\u5728\u591a\u4e2a\u6d4f\u89c8\u5668\u5f15\u64ce\u4e0a\u8fd0\u884c\uff0c\u8986\u76d6 Chrome\u3001Edge\u3001Firefox \u548c Safari \u7b49\u4e3b\u6d41\u6d4f\u89c8\u5668\u3002\n        <\/p>\n        <p class=\"en-only\">\n          Playwright is an open-source browser automation framework developed by Microsoft. It controls <strong>Chromium<\/strong>, <strong>Firefox<\/strong>, and <strong>WebKit<\/strong> through a unified API, enabling developers to write tests once and run them across multiple browser engines including Chrome, Edge, Firefox, and Safari.\n        <\/p>\n        <p class=\"zh-only\">\n          \u4e0e\u65e9\u671f\u7684\u81ea\u52a8\u5316\u5de5\u5177\u76f8\u6bd4\uff0cPlaywright \u9488\u5bf9\u73b0\u4ee3 Web \u5e94\u7528\u7684\u7279\u6027\u8fdb\u884c\u4e86\u6df1\u5ea6\u4f18\u5316\uff0c\u5305\u62ec\u5355\u9875\u5e94\u7528\uff08SPA\uff09\u3001\u52a8\u6001\u5185\u5bb9\u52a0\u8f7d\u3001Shadow DOM\u3001Web Components \u7b49\u590d\u6742\u573a\u666f\uff0c\u63d0\u4f9b\u4e86\u66f4\u7a33\u5b9a\u3001\u66f4\u5feb\u901f\u7684\u81ea\u52a8\u5316\u80fd\u529b\u3002\n        <\/p>\n        <p class=\"en-only\">\n          Compared to earlier automation tools, Playwright is deeply optimized for modern web application characteristics including SPAs, dynamic content loading, Shadow DOM, and Web Components, providing more stable and faster automation capabilities.\n        <\/p>\n      <\/section>\n\n      <!-- Features -->\n      <section id=\"features\" class=\"fade-in\">\n        <div class=\"section-header\">\n          <div class=\"section-number\">2<\/div>\n          <h2 class=\"zh-only\">\u6838\u5fc3\u7279\u6027<\/h2>\n          <h2 class=\"en-only\">Core Features<\/h2>\n        <\/div>\n        <div class=\"card-grid\">\n          <div class=\"card\">\n            <div class=\"card-icon\">&#127760;<\/div>\n            <h3 class=\"zh-only\">\u8de8\u6d4f\u89c8\u5668\u539f\u751f\u652f\u6301<\/h3>\n            <h3 class=\"en-only\">Cross-Browser Native<\/h3>\n            <p class=\"zh-only\">\u539f\u751f\u652f\u6301 Chromium\u3001Firefox \u548c WebKit\uff0c\u4e0d\u662f\u7b80\u5355\u7684\u6d4f\u89c8\u5668\u5c01\u88c5\uff0c\u800c\u662f\u76f4\u63a5\u901a\u8fc7\u5404\u5f15\u64ce\u7684 DevTools Protocol \u8fdb\u884c\u5e95\u5c42\u901a\u4fe1\u3002<\/p>\n            <p class=\"en-only\">Native support for Chromium, Firefox, and WebKit through direct DevTools Protocol communication at the engine level.<\/p>\n          <\/div>\n          <div class=\"card\">\n            <div class=\"card-icon\">&#128161;<\/div>\n            <h3 class=\"zh-only\">\u667a\u80fd\u81ea\u52a8\u7b49\u5f85<\/h3>\n            <h3 class=\"en-only\">Smart Auto-Waiting<\/h3>\n            <p class=\"zh-only\">\u6240\u6709\u64cd\u4f5c\u5728\u6267\u884c\u524d\u81ea\u52a8\u7b49\u5f85\u5143\u7d20\u5c31\u7eea\uff0c\u65e0\u9700\u663e\u5f0f\u6dfb\u52a0 sleep \u6216 wait\uff0c\u4ece\u6839\u672c\u4e0a\u51cf\u5c11\u4e0d\u7a33\u5b9a\u6d4b\u8bd5\uff08flaky tests\uff09\u3002<\/p>\n            <p class=\"en-only\">All actions automatically wait for elements to be ready before executing, eliminating the need for explicit sleeps and reducing flaky tests.<\/p>\n          <\/div>\n          <div class=\"card\">\n            <div class=\"card-icon\">&#128736;<\/div>\n            <h3 class=\"zh-only\">\u591a\u8bed\u8a00 SDK<\/h3>\n            <h3 class=\"en-only\">Multi-Language SDKs<\/h3>\n            <p class=\"zh-only\">\u63d0\u4f9b Python\u3001JavaScript\/TypeScript\u3001Java \u548c .NET \u7684\u5b98\u65b9 SDK\uff0c\u56e2\u961f\u53ef\u9009\u62e9\u6700\u719f\u6089\u7684\u6280\u672f\u6808\u3002<\/p>\n            <p class=\"en-only\">Official SDKs for Python, JavaScript\/TypeScript, Java, and .NET let teams choose their preferred stack.<\/p>\n          <\/div>\n          <div class=\"card\">\n            <div class=\"card-icon\">&#128200;<\/div>\n            <h3 class=\"zh-only\">\u6781\u901f\u5e76\u884c\u6267\u884c<\/h3>\n            <h3 class=\"en-only\">Fast Parallel Execution<\/h3>\n            <p class=\"zh-only\">\u57fa\u4e8e\u6d4f\u89c8\u5668\u4e0a\u4e0b\u6587\uff08Context\uff09\u7684\u9694\u79bb\u673a\u5236\uff0c\u53ef\u5728\u540c\u4e00\u6d4f\u89c8\u5668\u5b9e\u4f8b\u4e2d\u521b\u5efa\u6570\u767e\u4e2a\u72ec\u7acb\u73af\u5883\uff0c\u5b9e\u73b0\u6beb\u79d2\u7ea7\u542f\u52a8\u3002<\/p>\n            <p class=\"en-only\">Browser Context isolation allows hundreds of independent environments within a single browser instance, launching in milliseconds.<\/p>\n          <\/div>\n          <div class=\"card\">\n            <div class=\"card-icon\">&#128268;<\/div>\n            <h3 class=\"zh-only\">\u5f3a\u5927\u9009\u62e9\u5668\u7cfb\u7edf<\/h3>\n            <h3 class=\"en-only\">Powerful Selector System<\/h3>\n            <p class=\"zh-only\">\u652f\u6301 CSS\u3001XPath\u3001\u6587\u672c\u5185\u5bb9\u3001ARIA \u89d2\u8272\u3001\u6d4b\u8bd5 ID \u7b49\u591a\u79cd\u5b9a\u4f4d\u7b56\u7565\uff0c\u53ef\u7a7f\u900f Shadow DOM \u548c iframe\u3002<\/p>\n            <p class=\"en-only\">Supports CSS, XPath, text content, ARIA roles, test IDs, and can pierce through Shadow DOM and iframes.<\/p>\n          <\/div>\n          <div class=\"card\">\n            <div class=\"card-icon\">&#128737;<\/div>\n            <h3 class=\"zh-only\">\u5b8c\u6574\u8c03\u8bd5\u5de5\u5177\u94fe<\/h3>\n            <h3 class=\"en-only\">Complete Debug Toolkit<\/h3>\n            <p class=\"zh-only\">\u5185\u7f6e Codegen\uff08\u5f55\u5236\u751f\u6210\u811a\u672c\uff09\u3001Trace Viewer\uff08\u6267\u884c\u8ffd\u8e2a\u56de\u653e\uff09\u3001UI Mode\uff08\u4ea4\u4e92\u5f0f\u8c03\u8bd5\u6a21\u5f0f\uff09\u3002<\/p>\n            <p class=\"en-only\">Built-in Codegen, Trace Viewer for execution replay, and UI Mode for interactive debugging.<\/p>\n          <\/div>\n        <\/div>\n      <\/section>\n\n      <!-- Architecture -->\n      <section id=\"architecture\" class=\"fade-in\">\n        <div class=\"section-header\">\n          <div class=\"section-number\">3<\/div>\n          <h2 class=\"zh-only\">\u67b6\u6784\u8bbe\u8ba1<\/h2>\n          <h2 class=\"en-only\">Architecture<\/h2>\n        <\/div>\n        <p class=\"zh-only\">Playwright \u7684\u67b6\u6784\u56f4\u7ed5<strong>\u6d4f\u89c8\u5668\u4e0a\u4e0b\u6587\uff08Browser Context\uff09<\/strong>\u5c55\u5f00\u3002\u4e00\u4e2a\u6d4f\u89c8\u5668\u5b9e\u4f8b\u53ef\u521b\u5efa\u591a\u4e2a\u5b8c\u5168\u72ec\u7acb\u7684\u4e0a\u4e0b\u6587\uff0c\u6bcf\u4e2a\u4e0a\u4e0b\u6587\u62e5\u6709\u72ec\u7acb\u7684 Cookie\u3001LocalStorage\u3001\u7f13\u5b58\u548c\u4f1a\u8bdd\u72b6\u6001\u3002\u8fd9\u79cd\u8bbe\u8ba1\u53d6\u4ee3\u4e86\u4f20\u7edf\u81ea\u52a8\u5316\u4e2d&#8221;\u542f\u52a8\u591a\u4e2a\u6d4f\u89c8\u5668\u8fdb\u7a0b&#8221;\u7684\u91cd\u6a21\u5f0f\uff0c\u5927\u5e45\u964d\u4f4e\u4e86\u8d44\u6e90\u6d88\u8017\u548c\u542f\u52a8\u65f6\u95f4\u3002<\/p>\n        <p class=\"en-only\">Playwright&#8217;s architecture centers on <strong>Browser Contexts<\/strong>. A single browser instance can create multiple fully isolated contexts, each with independent cookies, localStorage, cache, and session state. This replaces the heavy &#8220;multiple browser processes&#8221; approach of traditional automation.<\/p>\n\n        <figure>\n          <div class=\"arch-diagram\">\n            <div class=\"arch-layer\">\n              <div class=\"arch-box mono\">Python \/ JS \/ Java \/ .NET<\/div>\n            <\/div>\n            <div class=\"arch-connector\"><\/div>\n            <div class=\"arch-layer\">\n              <div class=\"arch-box\">Playwright API<\/div>\n            <\/div>\n            <div class=\"arch-connector\"><\/div>\n            <div class=\"arch-layer\">\n              <div class=\"arch-box secondary\">Browser Context A<\/div>\n              <div class=\"arch-box secondary\">Browser Context B<\/div>\n              <div class=\"arch-box secondary\">Browser Context C<\/div>\n            <\/div>\n            <div class=\"arch-connector\"><\/div>\n            <div class=\"arch-layer\">\n              <div class=\"arch-box\">Chromium<\/div>\n              <div class=\"arch-box\">Firefox<\/div>\n              <div class=\"arch-box\">WebKit<\/div>\n            <\/div>\n          <\/div>\n          <figcaption class=\"zh-only\">\u56fe 1&nbsp;&nbsp;Playwright \u5206\u5c42\u67b6\u6784\u793a\u610f\u56fe<\/figcaption>\n          <figcaption class=\"en-only\">Figure 1&nbsp;&nbsp;Playwright Layered Architecture<\/figcaption>\n        <\/figure>\n      <\/section>\n\n      <!-- Code Examples -->\n      <section id=\"usage\" class=\"fade-in\">\n        <div class=\"section-header\">\n          <div class=\"section-number\">4<\/div>\n          <h2 class=\"zh-only\">\u57fa\u672c\u7528\u6cd5<\/h2>\n          <h2 class=\"en-only\">Basic Usage<\/h2>\n        <\/div>\n        <p class=\"zh-only\">\u4ee5\u4e0b\u793a\u4f8b\u5c55\u793a\u4e86\u5982\u4f55\u4f7f\u7528 Playwright \u6253\u5f00\u9875\u9762\u3001\u6267\u884c\u4ea4\u4e92\u64cd\u4f5c\u5e76\u8fdb\u884c\u65ad\u8a00\u9a8c\u8bc1\u3002<\/p>\n        <p class=\"en-only\">The following examples demonstrate how to open a page, perform interactions, and make assertions.<\/p>\n\n        <div class=\"code-block\">\n          <div class=\"code-header\">\n            <div class=\"code-tabs\">\n              <button class=\"code-tab active\" data-tab=\"py\">Python<\/button>\n              <button class=\"code-tab\" data-tab=\"js\">JavaScript<\/button>\n            <\/div>\n            <button class=\"copy-btn\" onclick=\"copyCode(this)\">\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"\/><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"\/><\/svg>\n              <span class=\"zh-only\">\u590d\u5236<\/span><span class=\"en-only\">Copy<\/span>\n            <\/button>\n          <\/div>\n          <div class=\"code-body\">\n            <div class=\"code-pane active\" id=\"pane-py\">\n              <div class=\"line-numbers\">1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11<br>12<br>13<br>14<\/div>\n              <pre><code><span class=\"keyword\">from<\/span> playwright.sync_api <span class=\"keyword\">import<\/span> sync_playwright\n\n<span class=\"keyword\">with<\/span> sync_playwright() <span class=\"keyword\">as<\/span> p:\n    browser = p.chromium.launch(headless=<span class=\"keyword\">False<\/span>)\n    context = browser.new_context()\n    page = context.new_page()\n\n    page.goto(<span class=\"string\">\"https:\/\/example.com\"<\/span>)\n    page.click(<span class=\"string\">\"button#submit\"<\/span>)\n\n    <span class=\"comment\"># \u65ad\u8a00\u4e0e\u622a\u56fe<\/span>\n    <span class=\"keyword\">assert<\/span> page.inner_text(<span class=\"string\">\"h1\"<\/span>) == <span class=\"string\">\"Welcome\"<\/span>\n    page.screenshot(path=<span class=\"string\">\"result.png\"<\/span>)\n\n    context.close()\n    browser.close()<\/code><\/pre>\n            <\/div>\n            <div class=\"code-pane\" id=\"pane-js\">\n              <div class=\"line-numbers\">1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11<br>12<br>13<br>14<br>15<\/div>\n              <pre><code><span class=\"keyword\">const<\/span> { chromium } = <span class=\"function\">require<\/span>(<span class=\"string\">'playwright'<\/span>);\n\n(<span class=\"keyword\">async<\/span> () => {\n  <span class=\"keyword\">const<\/span> browser = <span class=\"keyword\">await<\/span> chromium.launch({ headless: <span class=\"keyword\">false<\/span> });\n  <span class=\"keyword\">const<\/span> context = <span class=\"keyword\">await<\/span> browser.newContext();\n  <span class=\"keyword\">const<\/span> page = <span class=\"keyword\">await<\/span> context.newPage();\n\n  <span class=\"keyword\">await<\/span> page.goto(<span class=\"string\">'https:\/\/example.com'<\/span>);\n  <span class=\"keyword\">await<\/span> page.click(<span class=\"string\">'button#submit'<\/span>);\n\n  <span class=\"comment\">\/\/ Assertion and screenshot<\/span>\n  <span class=\"keyword\">const<\/span> title = <span class=\"keyword\">await<\/span> page.innerText(<span class=\"string\">'h1'<\/span>);\n  console.assert(title === <span class=\"string\">'Welcome'<\/span>);\n  <span class=\"keyword\">await<\/span> page.screenshot({ path: <span class=\"string\">'result.png'<\/span> });\n\n  <span class=\"keyword\">await<\/span> context.close();\n  <span class=\"keyword\">await<\/span> browser.close();\n})();<\/code><\/pre>\n            <\/div>\n          <\/div>\n        <\/div>\n      <\/section>\n\n      <!-- Scenarios -->\n      <section id=\"scenarios\" class=\"fade-in\">\n        <div class=\"section-header\">\n          <div class=\"section-number\">5<\/div>\n          <h2 class=\"zh-only\">\u5e94\u7528\u573a\u666f<\/h2>\n          <h2 class=\"en-only\">Use Cases<\/h2>\n        <\/div>\n        <div class=\"card-grid\">\n          <div class=\"card\">\n            <div class=\"card-icon\">&#9989;<\/div>\n            <h3 class=\"zh-only\">\u7aef\u5230\u7aef\u6d4b\u8bd5<\/h3>\n            <h3 class=\"en-only\">E2E Testing<\/h3>\n            <p class=\"zh-only\">\u6a21\u62df\u771f\u5b9e\u7528\u6237\u4ea4\u4e92\u8def\u5f84\uff0c\u9a8c\u8bc1\u5b8c\u6574\u4e1a\u52a1\u6d41\u7a0b\uff0c\u4ece\u767b\u5f55\u5230\u7ed3\u7b97\u7684\u5168\u94fe\u8def\u8986\u76d6\u3002<\/p>\n            <p class=\"en-only\">Simulate real user interaction paths to validate complete business flows from login to checkout.<\/p>\n          <\/div>\n          <div class=\"card\">\n            <div class=\"card-icon\">&#128269;<\/div>\n            <h3 class=\"zh-only\">\u7f51\u9875\u6570\u636e\u6293\u53d6<\/h3>\n            <h3 class=\"en-only\">Web Scraping<\/h3>\n            <p class=\"zh-only\">\u5229\u7528\u53ef\u9760\u7684\u81ea\u52a8\u7b49\u5f85\u673a\u5236\u548c\u9009\u62e9\u5668\u7cfb\u7edf\uff0c\u91c7\u96c6\u52a8\u6001\u6e32\u67d3\u7684\u73b0\u4ee3 Web \u9875\u9762\u5185\u5bb9\u3002<\/p>\n            <p class=\"en-only\">Leverage reliable auto-waiting and selector systems to scrape dynamically rendered modern web pages.<\/p>\n          <\/div>\n          <div class=\"card\">\n            <div class=\"card-icon\">&#128247;<\/div>\n            <h3 class=\"zh-only\">UI \u56de\u5f52\u6d4b\u8bd5<\/h3>\n            <h3 class=\"en-only\">UI Regression<\/h3>\n            <p class=\"zh-only\">\u7ed3\u5408\u622a\u56fe\u6bd4\u5bf9\u529f\u80fd\u68c0\u6d4b\u754c\u9762\u5f02\u5e38\u53d8\u5316\uff0c\u9632\u6b62\u89c6\u89c9\u5c42\u9762\u7684\u610f\u5916\u56de\u5f52\u3002<\/p>\n            <p class=\"en-only\">Detect visual regressions through screenshot comparisons to prevent unintended UI changes.<\/p>\n          <\/div>\n          <div class=\"card\">\n            <div class=\"card-icon\">&#128221;<\/div>\n            <h3 class=\"zh-only\">\u81ea\u52a8\u5316\u4efb\u52a1<\/h3>\n            <h3 class=\"en-only\">Automation Tasks<\/h3>\n            <p class=\"zh-only\">\u8868\u5355\u6279\u91cf\u586b\u5199\u3001\u6587\u4ef6\u4e0a\u4f20\u4e0b\u8f7d\u3001PDF \u751f\u6210\u3001\u5b9a\u65f6\u5de1\u68c0\u7b49\u91cd\u590d\u6027\u64cd\u4f5c\u3002<\/p>\n            <p class=\"en-only\">Batch form filling, file uploads\/downloads, PDF generation, and scheduled monitoring tasks.<\/p>\n          <\/div>\n        <\/div>\n      <\/section>\n\n      <!-- Comparison -->\n      <section id=\"comparison\" class=\"fade-in\">\n        <div class=\"section-header\">\n          <div class=\"section-number\">6<\/div>\n          <h2 class=\"zh-only\">\u4e0e Selenium \u7684\u5bf9\u6bd4<\/h2>\n          <h2 class=\"en-only\">Playwright vs Selenium<\/h2>\n        <\/div>\n        <div class=\"table-wrap\">\n          <table>\n            <thead>\n              <tr>\n                <th class=\"zh-only\">\u7ef4\u5ea6<\/th>\n                <th class=\"en-only\">Dimension<\/th>\n                <th>Playwright<\/th>\n                <th>Selenium<\/th>\n              <\/tr>\n            <\/thead>\n            <tbody>\n              <tr>\n                <td class=\"zh-only\">\u7b49\u5f85\u7b56\u7565<\/td>\n                <td class=\"en-only\">Waiting Strategy<\/td>\n                <td class=\"zh-only\">\u5185\u7f6e\u81ea\u52a8\u7b49\u5f85\uff0c\u9ed8\u8ba4\u7a33\u5b9a<\/td>\n                <td class=\"en-only\">Built-in auto-waiting<\/td>\n                <td class=\"zh-only\">\u9700\u663e\u5f0f\u7b49\u5f85\u6216\u4f7f\u7528\u5c01\u88c5\u5e93<\/td>\n                <td class=\"en-only\">Explicit waits or wrapper libs<\/td>\n              <\/tr>\n              <tr>\n                <td class=\"zh-only\">\u6d4f\u89c8\u5668\u63a7\u5236<\/td>\n                <td class=\"en-only\">Browser Control<\/td>\n                <td class=\"zh-only\">\u901a\u8fc7 CDP \u76f4\u63a5\u901a\u4fe1<\/td>\n                <td class=\"en-only\">Direct via CDP<\/td>\n                <td class=\"zh-only\">\u901a\u8fc7 WebDriver \u534f\u8bae\u4e2d\u8f6c<\/td>\n                <td class=\"en-only\">Via WebDriver protocol<\/td>\n              <\/tr>\n              <tr>\n                <td class=\"zh-only\">\u591a\u9875\u9762\/\u5f39\u7a97<\/td>\n                <td class=\"en-only\">Multi-page\/Popups<\/td>\n                <td class=\"zh-only\">\u539f\u751f\u652f\u6301\u591a\u6807\u7b7e\u9875\u3001iframe<\/td>\n                <td class=\"en-only\">Native multi-tab &#038; iframe<\/td>\n                <td class=\"zh-only\">\u5904\u7406\u76f8\u5bf9\u590d\u6742<\/td>\n                <td class=\"en-only\">More complex handling<\/td>\n              <\/tr>\n              <tr>\n                <td class=\"zh-only\">\u6267\u884c\u901f\u5ea6<\/td>\n                <td class=\"en-only\">Execution Speed<\/td>\n                <td class=\"zh-only\">\u901a\u5e38\u66f4\u5feb\uff0c\u4e0a\u4e0b\u6587\u5207\u6362\u5f00\u9500\u4f4e<\/td>\n                <td class=\"en-only\">Generally faster<\/td>\n                <td class=\"zh-only\">\u76f8\u5bf9\u8f83\u6162\uff0c\u5c24\u5176\u591a\u5b9e\u4f8b\u573a\u666f<\/td>\n                <td class=\"en-only\">Slower with multiple instances<\/td>\n              <\/tr>\n              <tr>\n                <td class=\"zh-only\">API \u73b0\u4ee3\u6027<\/td>\n                <td class=\"en-only\">API Modernity<\/td>\n                <td class=\"zh-only\">\u9488\u5bf9\u73b0\u4ee3 Web \u8bbe\u8ba1\uff08SPA\u3001Shadow DOM\uff09<\/td>\n                <td class=\"en-only\">Designed for modern web<\/td>\n                <td class=\"zh-only\">\u5386\u53f2\u5305\u88b1\u8f83\u91cd\uff0c\u517c\u5bb9\u65e7\u65b9\u6848<\/td>\n                <td class=\"en-only\">Legacy compatibility focus<\/td>\n              <\/tr>\n              <tr>\n                <td class=\"zh-only\">\u5de5\u5177\u94fe<\/td>\n                <td class=\"en-only\">Tooling<\/td>\n                <td class=\"zh-only\">Codegen\u3001Trace Viewer\u3001UI Mode<\/td>\n                <td class=\"en-only\">Codegen, Trace Viewer, UI Mode<\/td>\n                <td class=\"zh-only\">\u4e3b\u8981\u4f9d\u8d56\u7b2c\u4e09\u65b9\u751f\u6001<\/td>\n                <td class=\"en-only\">Relies on third-party ecosystem<\/td>\n              <\/tr>\n              <tr>\n                <td class=\"zh-only\">\u751f\u6001\u6210\u719f\u5ea6<\/td>\n                <td class=\"en-only\">Ecosystem<\/td>\n                <td class=\"zh-only\">\u8f83\u65b0\uff0c\u5de5\u5177\u94fe\u5b8c\u6574<\/td>\n                <td class=\"en-only\">Newer, complete toolchain<\/td>\n                <td class=\"zh-only\">\u5386\u53f2\u60a0\u4e45\uff0c\u793e\u533a\u548c\u7b2c\u4e09\u65b9\u5e93\u4e30\u5bcc<\/td>\n                <td class=\"en-only\">Mature, extensive community<\/td>\n              <\/tr>\n            <\/tbody>\n          <\/table>\n        <\/div>\n        <p class=\"table-caption zh-only\">\u88681&nbsp;&nbsp;Playwright \u4e0e Selenium \u591a\u7ef4\u5ea6\u5bf9\u6bd4<\/p>\n        <p class=\"table-caption en-only\">Table 1&nbsp;&nbsp;Playwright vs Selenium Comparison<\/p>\n      <\/section>\n\n      <!-- Summary -->\n      <section id=\"summary\" class=\"fade-in\">\n        <div class=\"section-header\">\n          <div class=\"section-number\">7<\/div>\n          <h2 class=\"zh-only\">\u603b\u7ed3<\/h2>\n          <h2 class=\"en-only\">Summary<\/h2>\n        <\/div>\n        <p class=\"zh-only\">\n          Playwright \u4ee3\u8868\u4e86\u6d4f\u89c8\u5668\u81ea\u52a8\u5316\u9886\u57df\u7684\u6700\u65b0\u53d1\u5c55\u65b9\u5411\u3002\u5176\u5728<strong>\u81ea\u52a8\u7b49\u5f85<\/strong>\u3001<strong>\u8de8\u6d4f\u89c8\u5668\u4e00\u81f4\u6027<\/strong>\u548c<strong>\u5f00\u53d1\u8005\u4f53\u9a8c<\/strong>\u65b9\u9762\u7684\u8bbe\u8ba1\uff0c\u4f7f\u5176\u6210\u4e3a\u73b0\u4ee3 Web \u5e94\u7528\u6d4b\u8bd5\u7684\u4f18\u5148\u9009\u62e9\u3002\u5bf9\u4e8e\u8ffd\u6c42\u6d4b\u8bd5\u7a33\u5b9a\u6027\u3001\u6267\u884c\u901f\u5ea6\u548c\u8c03\u8bd5\u6548\u7387\u7684\u56e2\u961f\uff0cPlaywright \u63d0\u4f9b\u4e86\u4ece\u7f16\u5199\u5230\u6267\u884c\u518d\u5230\u5206\u6790\u7684\u4e00\u7ad9\u5f0f\u89e3\u51b3\u65b9\u6848\u3002\n        <\/p>\n        <p class=\"en-only\">\n          Playwright represents the latest evolution in browser automation. Its design around <strong>auto-waiting<\/strong>, <strong>cross-browser consistency<\/strong>, and <strong>developer experience<\/strong> makes it the preferred choice for modern web application testing. Teams seeking test stability, execution speed, and debugging efficiency will find Playwright offers a comprehensive solution from authoring to analysis.\n        <\/p>\n        <div class=\"quick-start\">\n          <p class=\"zh-only\" style=\"color: var(--ink); margin-bottom: 0.75rem;\"><strong>\u5feb\u901f\u5f00\u59cb<\/strong><\/p>\n          <p class=\"en-only\" style=\"color: var(--ink); margin-bottom: 0.75rem;\"><strong>Quick Start<\/strong><\/p>\n          <pre><code><span class=\"comment\"># Python<\/span>\npip install playwright\nplaywright install\n\n<span class=\"comment\"># JavaScript<\/span>\nnpm init -y\nnpm install @playwright\/test\nnpx playwright install<\/code><\/pre>\n        <\/div>\n      <\/section>\n    <\/main>\n\n    <footer>\n      <p class=\"zh-only\">Playwright \u4ecb\u7ecd\u9875\u9762 \u00b7 \u57fa\u4e8e\u516c\u5f00\u6587\u6863\u6574\u7406<\/p>\n      <p class=\"en-only\">Playwright Introduction \u00b7 Based on public documentation<\/p>\n      <p style=\"margin-top: 0.5rem;\">\n        <a href=\"https:\/\/playwright.dev\" target=\"_blank\">playwright.dev<\/a>\n      <\/p>\n    <\/footer>\n  <\/div>\n\n  <!-- Back to Top -->\n  <button class=\"back-to-top\" id=\"backToTop\" title=\"\u8fd4\u56de\u9876\u90e8 \/ Back to top\">\n    <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n      <line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"5\"\/>\n      <polyline points=\"5 12 12 5 19 12\"\/>\n    <\/svg>\n  <\/button>\n\n  <script>\n    \/\/ \u6807\u8bb0 JS \u5df2\u542f\u7528\uff0c\u7528\u4e8e\u89e6\u53d1\u6eda\u52a8\u52a8\u753b\uff1b\u82e5\u811a\u672c\u4e0d\u53ef\u6267\u884c\uff0c.fade-in \u5185\u5bb9\u9ed8\u8ba4\u53ef\u89c1\uff0c\u4e0d\u4f1a\u7a7a\u767d\n    document.documentElement.classList.add('js');\n\n    \/\/ === Theme Toggle ===\n    const themeBtn = document.getElementById('themeBtn');\n    const themeIcon = document.getElementById('themeIcon');\n    let savedTheme = 'light';\n    try { savedTheme = localStorage.getItem('playwright-theme') || 'light'; } catch (e) {}\n    document.body.setAttribute('data-theme', savedTheme);\n    updateThemeIcon(savedTheme);\n\n    themeBtn.addEventListener('click', () => {\n      const current = document.body.getAttribute('data-theme');\n      const next = current === 'dark' ? 'light' : 'dark';\n      document.body.setAttribute('data-theme', next);\n      try { localStorage.setItem('playwright-theme', next); } catch (e) {}\n      updateThemeIcon(next);\n    });\n\n    function updateThemeIcon(theme) {\n      if (theme === 'dark') {\n        themeIcon.innerHTML = '<path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"\/>';\n      } else {\n        themeIcon.innerHTML = '<circle cx=\"12\" cy=\"12\" r=\"5\"\/><path d=\"M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42\"\/>';\n      }\n    }\n\n    \/\/ === Language Toggle ===\n    const langBtn = document.getElementById('langBtn');\n    const langZh = document.getElementById('langZh');\n    const langEn = document.getElementById('langEn');\n    let savedLang = 'zh';\n    try { savedLang = localStorage.getItem('playwright-lang') || 'zh'; } catch (e) {}\n    document.body.setAttribute('data-lang', savedLang);\n    updateLangHighlight(savedLang);\n\n    function updateLangHighlight(lang) {\n      if (lang === 'zh') {\n        langZh.style.opacity = '1';\n        langZh.style.fontWeight = '700';\n        langEn.style.opacity = '0.4';\n        langEn.style.fontWeight = '400';\n      } else {\n        langZh.style.opacity = '0.4';\n        langZh.style.fontWeight = '400';\n        langEn.style.opacity = '1';\n        langEn.style.fontWeight = '700';\n      }\n    }\n\n    langBtn.addEventListener('click', () => {\n      const current = document.body.getAttribute('data-lang');\n      const next = current === 'zh' ? 'en' : 'zh';\n      document.body.setAttribute('data-lang', next);\n      try { localStorage.setItem('playwright-lang', next); } catch (e) {}\n      updateLangHighlight(next);\n    });\n\n    \/\/ === Code Tabs ===\n    document.querySelectorAll('.code-tab').forEach(tab => {\n      tab.addEventListener('click', () => {\n        const parent = tab.closest('.code-block');\n        const tabName = tab.getAttribute('data-tab');\n        parent.querySelectorAll('.code-tab').forEach(t => t.classList.remove('active'));\n        parent.querySelectorAll('.code-pane').forEach(p => p.classList.remove('active'));\n        tab.classList.add('active');\n        parent.querySelector('#pane-' + tabName).classList.add('active');\n      });\n    });\n\n    \/\/ === Copy Code ===\n    function copyCode(btn) {\n      const block = btn.closest('.code-block');\n      const activePane = block.querySelector('.code-pane.active');\n      const code = activePane.querySelector('code').innerText;\n      navigator.clipboard.writeText(code).then(() => {\n        const span = btn.querySelector('span');\n        const original = span.textContent;\n        span.textContent = '\\u2713';\n        btn.style.color = 'var(--success)';\n        btn.style.borderColor = 'var(--success)';\n        setTimeout(() => {\n          span.textContent = original;\n          btn.style.color = '';\n          btn.style.borderColor = '';\n        }, 1500);\n      });\n    }\n\n    \/\/ === Reading Progress Bar ===\n    const progressBar = document.getElementById('progressBar');\n    const navbar = document.getElementById('navbar');\n\n    function updateProgress() {\n      const scrollTop = window.scrollY;\n      const docHeight = document.documentElement.scrollHeight - window.innerHeight;\n      const progress = docHeight > 0 ? (scrollTop \/ docHeight) * 100 : 0;\n      progressBar.style.width = progress + '%';\n\n      if (scrollTop > 10) {\n        navbar.classList.add('scrolled');\n      } else {\n        navbar.classList.remove('scrolled');\n      }\n    }\n\n    window.addEventListener('scroll', updateProgress, { passive: true });\n    updateProgress();\n\n    \/\/ === Back to Top ===\n    const backToTop = document.getElementById('backToTop');\n\n    function toggleBackToTop() {\n      if (window.scrollY > 400) {\n        backToTop.classList.add('visible');\n      } else {\n        backToTop.classList.remove('visible');\n      }\n    }\n\n    window.addEventListener('scroll', toggleBackToTop, { passive: true });\n\n    backToTop.addEventListener('click', () => {\n      window.scrollTo({ top: 0, behavior: 'smooth' });\n    });\n\n    \/\/ === Nav Scroll Spy ===\n    const navLinks = document.querySelectorAll('.nav-link');\n    const sections = document.querySelectorAll('section[id]');\n\n    navLinks.forEach(link => {\n      link.addEventListener('click', () => {\n        const targetId = link.getAttribute('data-target');\n        const target = document.getElementById(targetId);\n        if (target) {\n          target.scrollIntoView({ behavior: 'smooth', block: 'start' });\n        }\n      });\n    });\n\n    const spyObserver = new IntersectionObserver((entries) => {\n      entries.forEach(entry => {\n        if (entry.isIntersecting) {\n          const id = entry.target.getAttribute('id');\n          navLinks.forEach(link => {\n            link.classList.toggle('active', link.getAttribute('data-target') === id);\n          });\n        }\n      });\n    }, { rootMargin: '-20% 0px -70% 0px' });\n\n    sections.forEach(section => spyObserver.observe(section));\n\n    \/\/ === Scroll Animation ===\n    const observer = new IntersectionObserver((entries) => {\n      entries.forEach(entry => {\n        if (entry.isIntersecting) {\n          entry.target.classList.add('visible');\n        }\n      });\n    }, { threshold: 0.1 });\n\n    document.querySelectorAll('.fade-in').forEach(el => observer.observe(el));\n  <\/script>\n<\/body>\n<\/html>\n\n","protected":false},"excerpt":{"rendered":"<p>Playwright \u4ecb\u7ecd | Playwright Introduction Playwright \u6982\u8ff0Ov [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11,9],"tags":[],"class_list":["post-379","post","type-post","status-publish","format-standard","hentry","category-cad","category-ai"],"blocksy_meta":[],"_links":{"self":[{"href":"https:\/\/numsimlab.com\/index.php?rest_route=\/wp\/v2\/posts\/379","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/numsimlab.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/numsimlab.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/numsimlab.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/numsimlab.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=379"}],"version-history":[{"count":1,"href":"https:\/\/numsimlab.com\/index.php?rest_route=\/wp\/v2\/posts\/379\/revisions"}],"predecessor-version":[{"id":380,"href":"https:\/\/numsimlab.com\/index.php?rest_route=\/wp\/v2\/posts\/379\/revisions\/380"}],"wp:attachment":[{"href":"https:\/\/numsimlab.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=379"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/numsimlab.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=379"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/numsimlab.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=379"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}