<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Automation FC Blog: Playwright]]></title><description><![CDATA[Các bài viết cơ bản và nâng cao về Playwright]]></description><link>https://blog.automationfc.vn/s/playwright</link><image><url>https://substackcdn.com/image/fetch/$s_!5XlR!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e1c48af-fd94-4190-945e-91f2be65af76_1280x1280.png</url><title>Automation FC Blog: Playwright</title><link>https://blog.automationfc.vn/s/playwright</link></image><generator>Substack</generator><lastBuildDate>Fri, 30 Oct 2026 22:22:04 GMT</lastBuildDate><atom:link href="https://blog.automationfc.vn/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Automation FC]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[automationfc@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[automationfc@substack.com]]></itunes:email><itunes:name><![CDATA[Đào Minh Đảm]]></itunes:name></itunes:owner><itunes:author><![CDATA[Đào Minh Đảm]]></itunes:author><googleplay:owner><![CDATA[automationfc@substack.com]]></googleplay:owner><googleplay:email><![CDATA[automationfc@substack.com]]></googleplay:email><googleplay:author><![CDATA[Đào Minh Đảm]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Playwright Java Commands (Phần 1) ]]></title><description><![CDATA[Playwright, BrowserType, Browser v&#224; BrowserContext]]></description><link>https://blog.automationfc.vn/p/playwright-java-commands-phan-1</link><guid isPermaLink="false">https://blog.automationfc.vn/p/playwright-java-commands-phan-1</guid><dc:creator><![CDATA[Đào Minh Đảm]]></dc:creator><pubDate>Fri, 30 Oct 2026 20:11:12 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/8d6230f1-c903-4f5b-8695-72bf02bd9445_1312x1012.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>T&#7893;ng h&#7907;p c&#225;c l&#7879;nh n&#7873;n t&#7843;ng c&#7911;a Playwright Java, k&#232;m v&#237; d&#7909; v&#224; so s&#225;nh v&#7899;i Selenium WebDriver.</em></p><div><hr></div><h2>Y&#234;u c&#7847;u</h2><p>C&#225;c b&#7841;n &#273;&#227; quen v&#7899;i Selenium th&#236; khi chuy&#7875;n sang Playwright s&#7869; th&#7845;y m&#7897;t &#273;i&#7875;m kh&#225;c bi&#7879;t &#273;&#7847;u ti&#234;n: Selenium ch&#7881; c&#243; m&#7897;t &#273;&#7889;i t&#432;&#7907;ng <strong>WebDriver</strong> &#273;&#7875; l&#224;m m&#7885;i th&#7913;, c&#242;n Playwright chia th&#224;nh nhi&#7873;u t&#7847;ng, m&#7895;i t&#7847;ng &#273;&#7843;m nhi&#7879;m m&#7897;t nhi&#7879;m v&#7909; ri&#234;ng.</p><p>M&#236;nh hi&#7875;u r&#245; c&#226;y &#273;&#7889;i t&#432;&#7907;ng n&#224;y tr&#432;&#7899;c, sau &#273;&#243; h&#7885;c c&#225;c c&#226;u l&#7879;nh s&#7869; d&#7877; h&#417;n nhi&#7873;u v&#236; bi&#7871;t m&#7895;i thi&#7871;t l&#7853;p &#273;ang n&#7857;m &#7903; t&#7847;ng n&#224;o.</p><pre><code><code>Playwright
   &#9492;&#9472;&#9472; BrowserType        (chromium / firefox / webkit)
          &#9492;&#9472;&#9472; Browser           (1 instance tr&#236;nh duy&#7879;t &#273;ang ch&#7841;y)
                 &#9492;&#9472;&#9472; BrowserContext    (1 session &#273;&#7897;c l&#7853;p: cookie, storage...)
                        &#9492;&#9472;&#9472; Page              (1 tab)
</code></code></pre><p>Trong b&#224;i n&#224;y m&#236;nh s&#7869; t&#7893;ng h&#7907;p c&#225;c c&#226;u l&#7879;nh c&#7911;a 4 t&#7847;ng &#273;&#7847;u: <strong>Playwright &gt; BrowserType &gt; Browser &gt; BrowserContext</strong>. T&#7847;ng <strong>Page</strong> s&#7869; n&#7857;m &#7903; c&#225;c b&#224;i ti&#7871;p theo nh&#233;.</p><p>C&#225;c v&#237; d&#7909; vi&#7871;t b&#7857;ng <strong>Java</strong> - &#7913;ng d&#7909;ng demo l&#224; <strong><a href="https://opensource-demo.orangehrmlive.com/web/index.php">OrangeHRM</a></strong>.</p><h2>Gi&#7843;i ph&#225;p</h2><h3>1. Playwright</h3><p>&#272;&#226;y l&#224; &#273;&#7889;i t&#432;&#7907;ng g&#7889;c, &#273;&#7841;i di&#7879;n cho driver process c&#7911;a Playwright ch&#7841;y ng&#7847;m b&#234;n d&#432;&#7899;i.</p><ul><li><p><strong>Playwright.create()</strong>: kh&#7903;i t&#7841;o Playwright driver process</p></li><li><p><strong>playwright.chromium()</strong>: tr&#7843; v&#7873; BrowserType cho Chromium (d&#249;ng cho Chrome/ Edge)</p></li><li><p><strong>playwright.firefox()</strong>: tr&#7843; v&#7873; BrowserType cho Firefox</p></li><li><p><strong>playwright.webkit()</strong>: tr&#7843; v&#7873; BrowserType cho WebKit (engine c&#7911;a Safari)</p></li><li><p><strong>playwright.close()</strong>: &#273;&#243;ng driver process</p></li></ul><p>C&#225;c b&#7841;n n&#234;n d&#249;ng try-with-resources &#273;&#7875; Playwright t&#7921; &#273;&#7897;ng &#273;&#243;ng khi ch&#7841;y xong:</p><pre><code><code>try (Playwright playwright = Playwright.create()) {
    BrowserType chromium = playwright.chromium();
    Browser browser = chromium.launch();
    Page page = browser.newPage();
}
</code></code></pre><h3>2. BrowserType</h3><p>&#272;&#7841;i di&#7879;n cho m&#7897;t lo&#7841;i tr&#236;nh duy&#7879;t (chromium/ firefox/ webkit), d&#249;ng &#273;&#7875; launch instance.</p><ul><li><p><strong>launch()</strong>: kh&#7903;i ch&#7841;y browser m&#7899;i (m&#7863;c &#273;&#7883;nh headless = true)</p></li><li><p><strong>launchPersistentContext(userDataDir, options)</strong>: kh&#7903;i ch&#7841;y v&#7899;i profile l&#432;u trong th&#432; m&#7909;c (gi&#7919; session/ extension), tr&#7843; v&#7873; BrowserContext</p></li><li><p><strong>connect(wsEndpoint, options)</strong>: k&#7871;t n&#7889;i t&#7899;i browser/ Playwright server &#273;ang ch&#7841;y t&#7915; xa qua WebSocket (d&#249;ng cho c&#225;c n&#7873;n t&#7843;ng cloud h&#7895; tr&#7907; Playwright nh&#432; BrowserStack...)</p></li><li><p><strong>connectOverCDP(endpointURL)</strong>: k&#7871;t n&#7889;i qua Chrome DevTools Protocol (ch&#7881; Chromium)</p></li><li><p><strong>executablePath()</strong>: tr&#7843; v&#7873; &#273;&#432;&#7901;ng d&#7851;n t&#7899;i binary tr&#236;nh duy&#7879;t m&#224; Playwright s&#7869; d&#249;ng</p></li><li><p><strong>name()</strong>: tr&#7843; v&#7873; t&#234;n engine: &#8220;chromium&#8221; - &#8220;firefox&#8221; - &#8220;webkit&#8221;</p></li></ul><p><strong>V&#237; d&#7909; 1:</strong> ch&#7841;y c&#243; giao di&#7879;n v&#224; ch&#7853;m l&#7841;i &#273;&#7875; d&#7877; debug</p><pre><code><code>Browser browser = playwright.chromium().launch(
    new BrowserType.LaunchOptions()
        .setHeadless(false)        // t&#432;&#417;ng &#273;&#432;&#417;ng ChromeOptions kh&#244;ng add "--headless"
        .setSlowMo(50)             // delay 50ms gi&#7919;a m&#7895;i action - h&#7919;u &#237;ch khi debug
        .setArgs(List.of("--start-maximized"))
);
</code></code></pre><p><strong>L&#432;u &#253;:</strong> <code>--start-maximized</code> ch&#7881; c&#243; t&#225;c d&#7909;ng khi context &#273;&#432;&#7907;c t&#7841;o v&#7899;i viewport = null: <code>browser.newContext(new Browser.NewContextOptions().setViewportSize(null))</code>. N&#7871;u kh&#244;ng th&#236; Playwright v&#7851;n d&#249;ng viewport m&#7863;c &#273;&#7883;nh.</p><p><strong>V&#237; d&#7909; 2:</strong> d&#249;ng l&#7841;i profile &#273;&#7875; gi&#7919; session &#273;&#259;ng nh&#7853;p gi&#7919;a c&#225;c l&#7847;n ch&#7841;y</p><pre><code><code>BrowserContext context = playwright.chromium().launchPersistentContext(
    Paths.get("/tmp/orangehrm-profile"),
    new BrowserType.LaunchPersistentContextOptions().setHeadless(false)
);
Page page = context.newPage();
</code></code></pre><p><strong>V&#237; d&#7909; 3:</strong> k&#7871;t n&#7889;i t&#7899;i browser &#7903; xa</p><pre><code><code>Browser browser = playwright.chromium()
    .connect("wss://your-grid-endpoint.com/ws?token=xxx");
</code></code></pre><h3>3. Browser</h3><p>&#272;&#7841;i di&#7879;n cho m&#7897;t instance tr&#236;nh duy&#7879;t &#273;&#227; kh&#7903;i ch&#7841;y. T&#7915; m&#7897;t Browser, ch&#250;ng ta c&#243; th&#7875; t&#7841;o nhi&#7873;u BrowserContext &#273;&#7897;c l&#7853;p.</p><ul><li><p><strong>newContext() / newContext(options)</strong>: t&#7841;o context m&#7899;i (nh&#432; 1 c&#7917;a s&#7893; &#7849;n danh ri&#234;ng bi&#7879;t, cookie/ storage &#273;&#7897;c l&#7853;p)</p></li><li><p><strong>newPage()</strong>: t&#7841;o context + page trong 1 b&#432;&#7899;c (d&#249;ng khi kh&#244;ng c&#7847;n custom context)</p></li><li><p><strong>contexts()</strong>: tr&#7843; v&#7873; List&lt;BrowserContext&gt; &#273;ang m&#7903;</p></li><li><p><strong>isConnected()</strong>: ki&#7875;m tra browser c&#242;n k&#7871;t n&#7889;i hay kh&#244;ng</p></li><li><p><strong>version()</strong>: tr&#7843; v&#7873; version string c&#7911;a browser engine</p></li><li><p><strong>newBrowserCDPSession()</strong>: t&#7841;o phi&#234;n CDP m&#7899;i (ch&#7881; Chromium) &#273;&#7875; g&#7885;i API DevTools tr&#7921;c ti&#7871;p</p></li><li><p><strong>startTracing(page, options) / stopTracing()</strong>: b&#7855;t &#273;&#7847;u/ k&#7871;t th&#250;c ghi trace &#7903; m&#7913;c browser (ch&#7881; Chromium)</p></li><li><p><strong>close()</strong>: &#273;&#243;ng to&#224;n b&#7897; browser process v&#224; m&#7885;i context/ page b&#234;n trong</p></li></ul><p><strong>V&#237; d&#7909;:</strong> 2 user &#273;&#259;ng nh&#7853;p song song trong c&#249;ng 1 browser</p><pre><code><code>Browser browser = playwright.chromium().launch();

BrowserContext adminContext = browser.newContext();
BrowserContext essContext   = browser.newContext();

Page adminPage = adminContext.newPage();
Page essPage   = essContext.newPage();
// 2 page n&#224;y kh&#244;ng d&#249;ng chung cookie/ session v&#7899;i nhau
</code></code></pre><p>C&#225;ch n&#224;y r&#7845;t h&#7919;u &#237;ch khi test c&#225;c case nhi&#7873;u role (Admin - ESS) m&#224; kh&#244;ng c&#7847;n m&#7903; 2 tr&#236;nh duy&#7879;t.</p><h3>4. BrowserContext</h3><p>T&#432;&#417;ng &#273;&#432;&#417;ng m&#7897;t session &#273;&#7897;c l&#7853;p, qu&#7843;n l&#253; cookie/ storage/ permission/ network &#7903; c&#7845;p to&#224;n context (&#225;p d&#7909;ng cho m&#7885;i page t&#7841;o ra t&#7915; n&#243;). M&#236;nh chia th&#224;nh 4 nh&#243;m:</p><p><strong>4.1. Qu&#7843;n l&#253; Page/ v&#242;ng &#273;&#7901;i</strong></p><ul><li><p><strong>newPage()</strong>: m&#7903; tab/ page m&#7899;i trong context n&#224;y</p></li><li><p><strong>pages()</strong>: tr&#7843; v&#7873; List&lt;Page&gt; &#273;ang m&#7903; trong context</p></li><li><p><strong>browser()</strong>: tr&#7843; v&#7873; Browser cha c&#7911;a context n&#224;y</p></li><li><p><strong>close()</strong>: &#273;&#243;ng context v&#224; to&#224;n b&#7897; page b&#234;n trong</p></li></ul><p><strong>4.2. Cookie - Storage</strong></p><p>T&#432;&#417;ng t&#7921; <code>driver.manage().getCookies()</code> c&#7911;a Selenium.</p><ul><li><p><strong>cookies() / cookies(urls)</strong>: l&#7845;y danh s&#225;ch cookie hi&#7879;n t&#7841;i</p></li><li><p><strong>addCookies(List&lt;Cookie&gt;)</strong>: inject cookie v&#224;o context</p></li><li><p><strong>clearCookies()</strong>: x&#243;a to&#224;n b&#7897; cookie</p></li><li><p><strong>storageState() / storageState(options)</strong>: xu&#7845;t to&#224;n b&#7897; state (cookie + localStorage) ra JSON, d&#249;ng &#273;&#7875; l&#432;u session v&#224; t&#225;i s&#7917; d&#7909;ng gi&#7919;a c&#225;c l&#7847;n ch&#7841;y test</p></li></ul><p><strong>V&#237; d&#7909;:</strong> &#273;&#259;ng nh&#7853;p 1 l&#7847;n, l&#432;u l&#7841;i, c&#225;c test sau d&#249;ng l&#7841;i</p><pre><code><code>// Sau khi &#273;&#259;ng nh&#7853;p th&#224;nh c&#244;ng
context.storageState(
    new BrowserContext.StorageStateOptions().setPath(Paths.get("auth/admin.json"))
);

// &#7902; c&#225;c test sau: n&#7841;p l&#7841;i session, b&#7887; qua b&#432;&#7899;c login
BrowserContext loggedIn = browser.newContext(
    new Browser.NewContextOptions().setStorageStatePath(Paths.get("auth/admin.json"))
);
</code></code></pre><p><strong>4.3. Network Interception</strong></p><ul><li><p><strong>route(url, handler)</strong>: ch&#7863;n/ gi&#7843; l&#7853;p request</p></li><li><p><strong>unroute(url)</strong>: g&#7905; b&#7887; route &#273;&#227; &#273;&#259;ng k&#253;</p></li><li><p><strong>setExtraHTTPHeaders(headers)</strong>: set header m&#7863;c &#273;&#7883;nh cho m&#7885;i request</p></li><li><p><strong>setOffline(true/false)</strong>: gi&#7843; l&#7853;p m&#7845;t m&#7841;ng</p></li><li><p><strong>setGeolocation(geolocation)</strong>: gi&#7843; l&#7853;p v&#7883; tr&#237; &#273;&#7883;a l&#253;</p></li><li><p><strong>onRequest(handler) / onResponse(handler)</strong>: log/ theo d&#245;i m&#7885;i network traffic trong context</p></li></ul><pre><code><code>// Ch&#7863;n to&#224;n b&#7897; h&#236;nh &#7843;nh &#273;&#7875; trang load nhanh h&#417;n
context.route("**/*.{png,jpg,jpeg}", route -&gt; route.abort());

// Log m&#7885;i request
context.onRequest(request -&gt;
    System.out.println("&gt;&gt; " + request.method() + " " + request.url()));

// Gi&#7843; l&#7853;p m&#7845;t m&#7841;ng r&#7891;i b&#7853;t l&#7841;i
context.setOffline(true);
// ... ki&#7875;m tra th&#244;ng b&#225;o l&#7895;i c&#7911;a &#7913;ng d&#7909;ng
context.setOffline(false);
</code></code></pre><p><strong>4.4. Timeout - Permission</strong></p><ul><li><p><strong>setDefaultTimeout(ms)</strong>: set timeout m&#7863;c &#273;&#7883;nh cho m&#7885;i action trong context (g&#7847;n gi&#7889;ng implicit wait c&#7911;a Selenium &#7903; ch&#7895; set 1 l&#7847;n d&#249;ng cho to&#224;n b&#7897;, nh&#432;ng b&#7843;n ch&#7845;t kh&#225;c v&#236; Playwright &#273;&#227; c&#243; c&#417; ch&#7871; auto-wait cho t&#7915;ng action)</p></li><li><p><strong>setDefaultNavigationTimeout(ms)</strong>: timeout ri&#234;ng cho navigate()</p></li><li><p><strong>grantPermissions(List&lt;String&gt;)</strong>: c&#7845;p quy&#7873;n browser (camera/ geolocation/ notifications/..)</p></li><li><p><strong>clearPermissions()</strong>: x&#243;a to&#224;n b&#7897; quy&#7873;n &#273;&#227; c&#7845;p</p></li></ul><pre><code><code>context.setDefaultTimeout(10_000);            // 10 gi&#226;y cho m&#7895;i action
context.setDefaultNavigationTimeout(30_000);  // 30 gi&#226;y cho navigate

context.grantPermissions(List.of("geolocation"));
context.setGeolocation(new Geolocation(16.0544, 108.2022)); // &#272;&#224; N&#7861;ng
</code></code></pre><h2>Source demo</h2><p>M&#236;nh g&#7897;p c&#225;c l&#7879;nh &#7903; tr&#234;n th&#224;nh 1 v&#237; d&#7909; ho&#224;n ch&#7881;nh v&#7899;i OrangeHRM, c&#225;c b&#7841;n ch&#7841;y th&#7917; v&#224; t&#249;y bi&#7871;n theo nhu c&#7847;u nh&#233;:</p><pre><code><code>import com.microsoft.playwright.*;
import java.nio.file.Paths;

public class Topic01Demo {
    public static void main(String[] args) {
        try (Playwright playwright = Playwright.create()) {
            Browser browser = playwright.chromium().launch(
                new BrowserType.LaunchOptions().setHeadless(false).setSlowMo(50));

            BrowserContext context = browser.newContext();
            context.setDefaultTimeout(10_000);
            context.onRequest(r -&gt; System.out.println("&gt;&gt; " + r.method() + " " + r.url()));

            Page page = context.newPage();
            page.navigate("https://opensource-demo.orangehrmlive.com/");
            System.out.println("Title: " + page.title());

            context.storageState(
                new BrowserContext.StorageStateOptions().setPath(Paths.get("state.json")));

            browser.close();
        }
    }
}
</code></code></pre><h2>T&#243;m t&#7855;t</h2><ul><li><p><strong>Playwright</strong>: kh&#7903;i t&#7841;o driver, tr&#7843; v&#7873; BrowserType</p></li><li><p><strong>BrowserType</strong>: ch&#7885;n engine v&#224; c&#225;ch kh&#7903;i ch&#7841;y (launch, launchPersistentContext, connect, connectOverCDP)</p></li><li><p><strong>Browser</strong>: 1 tr&#236;nh duy&#7879;t &#273;ang ch&#7841;y, t&#7841;o &#273;&#432;&#7907;c nhi&#7873;u context &#273;&#7897;c l&#7853;p</p></li><li><p><strong>BrowserContext</strong>: 1 session qu&#7843;n l&#253; cookie, storage, network, timeout v&#224; permission cho m&#7885;i page b&#234;n trong</p></li></ul><h2>Reference</h2><ul><li><p><a href="https://playwright.dev/java/docs/api/class-playwright">Playwright Java - class Playwright</a></p></li><li><p><a href="https://playwright.dev/java/docs/api/class-browsertype">Playwright Java - class BrowserType</a></p></li><li><p><a href="https://playwright.dev/java/docs/api/class-browser">Playwright Java - class Browser</a></p></li><li><p><a href="https://playwright.dev/java/docs/api/class-browsercontext">Playwright Java - class BrowserContext</a></p></li></ul><div><hr></div><p>C&#225;c b&#224;i vi&#7871;t kh&#225;c v&#7873; Testing/ Automation Testing c&#225;c b&#7841;n xem th&#234;m t&#7841;i <a href="https://blog.automationfc.vn/">blog.automationfc.vn</a> v&#224; <a href="https://automationfc.vn/">automationfc.vn</a> nh&#233;.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.automationfc.vn/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.automationfc.vn/subscribe?"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item></channel></rss>