npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xylentis/testgen

v1.2.2

Published

Record a UI test once with the Playwright recorder, then generate it for Playwright (TypeScript, Go) and Selenium (Java, JavaScript).

Readme

Automation Test Code: testgen

English · Tiếng Việt

Ghi thao tác trên web một lần bằng recorder của Playwright, rồi sinh test UI cho nhiều framework và nhiều ngôn ngữ. Cách dùng giống npx playwright codegen, nhưng không chỉ sinh code Playwright mà còn sinh cả Selenium, và thêm ngôn ngữ Go. Trong giao diện, bạn sửa được từng bước, chạy thử lại (Replay) và xem các cảnh báo trước khi lưu test: ghi → sửa → chạy thử → xuất.

Hướng dẫn từng bước, có ví dụ cho từng target: Hướng dẫn sử dụng (docs/huong-dan-su-dung.md).

Giao diện testgen

| Target | Framework | Ngôn ngữ | File sinh ra | | --- | --- | --- | --- | | playwright-ts | Playwright Test (@playwright/test) | TypeScript | login.spec.ts | | playwright-go | playwright-go + testify | Go | login_test.go | | selenium-java | Selenium 4 + JUnit 5 | Java 17+ | LoginTest.java + TestgenSupport.java | | selenium-java-testng | Selenium 4 + TestNG | Java 17+ | LoginTest.java + TestgenSupport.java | | selenium-js | Selenium 4 + Mocha | JavaScript | login.spec.js + testgen-support.js |

Cài đặt

Cần Node.js 22+ (khuyên dùng bản LTS 24).

npm install -g @xylentis/testgen     # cài lệnh `testgen`
testgen ui

npx @xylentis/testgen ui             # hoặc chạy luôn, không cần cài
npm i -D @xylentis/testgen           # hoặc cài vào project, rồi gọi `npx testgen ...`

Để ghi thao tác cần một trình duyệt. Nếu máy đã có Google Chrome thì chọn Chrome là xong. Nếu muốn dùng Chromium của Playwright thì tải về bằng:

npx playwright-core install chromium

Dùng giao diện (khuyên dùng)

testgen ui                  # mở http://127.0.0.1:9323
  1. Nhập URL, chọn trình duyệt rồi bấm Record.
  2. Một cửa sổ trình duyệt mở ra. Bạn thao tác như người dùng thật: click, gõ, chọn…
    • Thanh công cụ của recorder nằm ở đầu trang, dùng để thêm assert: Assert visibility, Assert text, Assert value.
    • Cửa sổ Playwright Inspector được ẩn đi, chỉ còn trình duyệt bạn đang thao tác. Muốn hiện lại thì dùng testgen record --inspector.
  3. Trong khi bạn thao tác, giao diện testgen cập nhật danh sách bước và code của mọi target.
  4. Đóng trình duyệt hoặc bấm Stop khi xong.
  5. Sửa các bước nếu cần, bấm Replay để chạy thử, rồi xem mục Checks (xem bên dưới).
  6. Với từng target, bạn có thể Copy, Download hoặc Save files để lưu thẳng vào thư mục project. Đường dẫn tính từ thư mục bạn chạy testgen ui.

Một số thao tác khác:

  • Đổi Test name / Package: code sinh lại ngay.
  • Test id attribute: đặt trước khi bấm Record. Code luôn dùng thuộc tính mà bản ghi đã dùng, nên đổi sau khi ghi chỉ có tác dụng cho lần ghi sau. Giao diện hiện ghi chú khi hai giá trị khác nhau. Xem thêm mục Locator và web nhiều ngôn ngữ bên dưới.
  • Log steps (target Selenium) và Data-driven (CSV) (mọi target): xem mục Log từng bước và test data-driven bên dưới.
  • Download recording: lưu file .jsonl để lần sau sinh lại. File không chứa mật khẩu đã gõ, xem mục Mật khẩu và dữ liệu nhạy cảm.
  • Open recording…: mở lại một file đã lưu.

Sửa bước, Replay và Checks

  • Sửa bước: rê chuột (hoặc Tab) vào một bước để hiện Edit, ↑, ↓, ✕. Edit cho sửa:

    • locator, viết như trong test Playwright: getByRole('button', { name: 'Đăng nhập' }), getByLabel('Email'), getByTestId('save'), locator('#email');
    • chữ đã gõ, giá trị đã chọn, chữ hoặc giá trị mong đợi của assert, phím (Enter, Shift+Tab), URL, đường dẫn file upload;
    • ô Secret của bước gõ chữ: giá trị đọc từ biến môi trường nào.

    ↑ ↓ đổi thứ tự, ✕ xóa, Undo quay lại thay đổi trước. testgen chặn những thay đổi làm một trang bị dùng trước khi mở hoặc sau khi đóng, ví dụ xóa bước mở popup khi các bước sau vẫn thao tác trên popup đó.

  • Replay: chạy lại các bước bằng Playwright, trong trình duyệt của bản ghi. Mỗi bước hiện ✓ hoặc ✗. Bước lỗi hiện thông báo và ảnh chụp trang lúc lỗi; bấm Edit step N để sửa rồi Replay lại. Replay chạy chính bản ghi, nên nó cho biết locator và dữ liệu còn đúng trên Playwright, chưa cho biết code Selenium có chạy được hay không (xem Giới hạn hiện tại).

  • Checks: những chỗ nên xem lại trước khi dùng test, ví dụ bản ghi không có assertion nào, các bước cuối không được kiểm tra, locator dựa vào vị trí (nth(1)) hay cấu trúc trang (XPath), file upload chỉ có tên, và những gì target đang xem không làm được (// TODO(testgen)). Bấm vào một dòng để nhảy tới bước đó. Lệnh testgen record và testgen generate in các cảnh báo này ra sau khi sinh code.

Dùng dòng lệnh

# Ghi rồi sinh luôn (đóng trình duyệt là sinh). Mặc định ra ./generated/<target>/
testgen record https://your-app.example --channel chrome

# Chỉ một vài target, chọn thư mục ra
testgen record https://your-app.example -t selenium-java,playwright-go -d tests/generated

# Sinh lại từ một bản ghi đã lưu
testgen generate generated/recording.jsonl -t selenium-java -o src/test/java/com/acme/e2e/LoginTest.java
testgen generate generated/recording.jsonl -t playwright-go -o e2e/login_test.go
testgen generate generated/recording.jsonl -d out          # tất cả target, mỗi target một thư mục

# TestNG, log từng bước, dữ liệu đọc từ src/test/resources/com/acme/e2e/SearchTest.csv
testgen generate generated/recording.jsonl -t selenium-java-testng --log-steps --data-driven \
  -o src/test/java/com/acme/e2e/SearchTest.java

# Playwright, dữ liệu đọc từ tests/search.csv; Go đọc từ e2e/testdata/search.csv
testgen generate generated/recording.jsonl -t playwright-ts --data-driven -o tests/search.spec.ts
testgen generate generated/recording.jsonl -t playwright-go --data-driven -o e2e/search_test.go

testgen targets                                            # liệt kê target

| Tùy chọn | Ý nghĩa | | --- | --- | | -t, --target | Danh sách target, cách nhau bằng dấu phẩy. Mặc định là tất cả. | | -o, --output | File test cần ghi, chỉ dùng khi có 1 target. File hỗ trợ (support) được đặt cạnh file test. | | -d, --out-dir | Thư mục ra. Nếu có nhiều target thì mỗi target một thư mục con. | | -n, --name | Tên test. Mặc định lấy từ tên file --output, nếu không có thì là recorded. | | --package | Package cho Java/Go. Java tự lấy từ đường dẫn sau src/test/java/, Go tự lấy từ tên thư mục. | | --test-id-attribute | Thuộc tính recorder dùng cho getByTestId (mặc định data-testid). Khi sinh code, testgen dùng thuộc tính đã lưu trong bản ghi; tùy chọn này chỉ có tác dụng với bản ghi không có bước getByTestId nào. | | --log-steps | (Selenium) Ghi log từng bước và kết quả từng assert khi test chạy. | | --data-driven | Giá trị đã gõ, chọn và assert thành cột của một file CSV, test chạy một lần cho mỗi dòng. File CSV đã có không bị ghi đè. | | --save | (record) Nơi lưu bản ghi .jsonl, không kèm mật khẩu đã gõ. | | --browser, --channel, --device, --viewport-size, --lang, --timezone, --color-scheme, --user-agent, --ignore-https-errors, --load-storage, --save-storage | (record) Giống hệt playwright codegen. | | --inspector | (record) Hiện thêm cửa sổ Playwright Inspector (mặc định ẩn). |

Locator và web nhiều ngôn ngữ (i18n)

testgen giữ nguyên locator mà recorder của Playwright chọn. Recorder ưu tiên theo thứ tự:

  1. test id: getByTestId('welcome'), theo thuộc tính data-testid hoặc thuộc tính đặt bằng --test-id-attribute;
  2. role + tên (accessible name, thường là chữ hiển thị): getByRole('button', { name: 'Sign in' });
  3. placeholder, label, alt, text, title;
  4. #id; sau đó mới đến tên thẻ và vị trí.

#id xếp sau vì id thường do framework sinh ra và đổi giữa các lần build (:r1: của React, mat-input-0 của Angular Material). Id trông như chuỗi ngẫu nhiên thì recorder bỏ qua hẳn. Ví dụ trong app demo, <input id="email"> có label "Email" nên thành getByRole('textbox', { name: 'Email' }) chứ không phải #email.

Vì vậy, nếu app không có test id, phần lớn locator dựa vào chữ trên trang. Test ghi trên giao diện tiếng Anh sẽ gãy khi chạy trên giao diện tiếng Việt: getByRole('button', { name: 'Sign in' }) không khớp với nút "Đăng nhập". Có ba cách xử lý:

  • Cố định ngôn ngữ khi chạy test. Ghi với --lang, code sinh ra sẽ đặt cùng locale (Selenium: thêm --lang cho Chrome và Edge):

    testgen record https://your-app.example --lang en-US

    Cách này chỉ có tác dụng khi app chọn ngôn ngữ theo trình duyệt. Nếu app chọn theo URL (/en/...), cookie hay cài đặt tài khoản thì cần cố định ngôn ngữ ở đó, ví dụ ghi từ URL /en/... hoặc dùng tài khoản test có ngôn ngữ cố định.

  • Thêm test id vào app. Đây là cách bền nhất khi một test phải chạy trên nhiều ngôn ngữ, vì test id luôn được ưu tiên. App dùng thuộc tính khác thì khai báo khi ghi, ví dụ --test-id-attribute data-qa.

  • Dùng id sẵn có làm test id: --test-id-attribute id, hoặc nhập id vào ô Test id attribute trong giao diện. Phần tử có id thành getByTestId('email'), phần tử không có id vẫn dùng role + tên. Chỉ nên dùng khi id do người viết đặt, vì với test id recorder không lọc các id sinh tự động.

Thuộc tính test id được lưu trong bản ghi, và code sinh ra luôn dùng đúng thuộc tính đó. Vì vậy hãy chọn thuộc tính trước khi ghi. --test-id-attribute khi chạy generate chỉ có tác dụng với bản ghi không có bước getByTestId nào.

Mật khẩu và dữ liệu nhạy cảm

Recorder của Playwright ghi lại mọi thứ bạn gõ, kể cả mật khẩu. testgen không để giá trị đó vào test:

  • Phát hiện: bước gõ chữ vào ô mà locator gọi là mật khẩu, PIN, OTP, mã xác thực, token, API key… (tiếng Anh, tiếng Việt và vài ngôn ngữ khác, ví dụ getByRole('textbox', { name: 'Mật khẩu' }), #txtPwd, input[type="password"]) là bước secret.

  • Code sinh ra đọc biến môi trường đặt tên theo ô đó: TEST_PASSWORD, TEST_MAT_KHAU, TEST_OLD_PASSWORD… Gõ cùng một giá trị hai lần (mật khẩu và xác nhận mật khẩu) thì dùng chung một biến. Assert kiểm tra đúng giá trị đó cũng đọc biến này.

    page.getByRole("textbox", "Password").fill(TestgenSupport.secret("TEST_PASSWORD"));   // Java
    await page.getByRole('textbox', { name: 'Password' }).fill(secret('TEST_PASSWORD'));  // TypeScript, JavaScript
    testPassword, ok := os.LookupEnv("TEST_PASSWORD")                                     // Go
    require.True(t, ok, "set the environment variable TEST_PASSWORD, ...")

    Thiếu biến thì test dừng ngay ở đầu với thông báo nêu tên biến. Java đọc cả system property, nên mvn test -DTEST_PASSWORD=... cũng được.

  • Không lưu giá trị: bản ghi do testgen record lưu và file của Download recording thay giá trị bằng "" và ghi tên biến vào bước ("secret": "TEST_PASSWORD"). Sinh lại từ file này cho đúng code cũ. Replay một bản ghi như vậy thì giao diện hỏi giá trị và chỉ giữ trong bộ nhớ của testgen ui. testgen generate nhắc khi file bản ghi cũ vẫn còn giá trị.

  • Secret nằm trong chữ khác (ví dụ assert trên phần tử hiện đúng mật khẩu, hay chữ mong đợi Saved for hunter2!): code dùng secret(...) ở đúng chỗ đó, ví dụ page.getByText(secret('TEST_PASSWORD')), còn bản ghi lưu ${TEST_PASSWORD} thay cho giá trị.

  • Log: --log-steps, thông báo lỗi của Selenium và của Replay hiện giá trị secret là ***.

  • Sửa khi phát hiện sai: trong giao diện, Edit một bước gõ chữ rồi bật/tắt ô Secret hoặc đổi tên biến. Bước đã là secret vẫn là secret khi bạn sửa locator của nó. Với CLI, thêm "secret": false (không phải secret) hoặc "secret": "TEN_BIEN" vào dòng của bước đó trong file .jsonl.

Chạy test đã sinh

Trong e2e/projects/ có sẵn project mẫu cho từng target. Đó là đúng các project dùng để kiểm thử testgen, nên copy về dùng ngay được.

playwright-ts: cần @playwright/test (xem e2e/projects/playwright-ts):

npm i -D @playwright/test && npx playwright install chromium
npx playwright test

playwright-go: lưu ý module path hiện tại là github.com/mxschmitt/playwright-go:

go get github.com/mxschmitt/[email protected] github.com/stretchr/testify
go run github.com/mxschmitt/playwright-go/cmd/playwright install --with-deps chromium
go test ./...                 # HEADED=1 go test ./... để xem trình duyệt

selenium-java: cần selenium-java và junit-jupiter (xem e2e/projects/selenium-java/pom.xml). LoginTest.java và TestgenSupport.java phải nằm cùng package. Chạy bằng mvn test.

selenium-java-testng: giống selenium-java nhưng chạy bằng TestNG (@BeforeMethod, @AfterMethod, @Test), cần selenium-java và testng (xem e2e/projects/selenium-java-testng/pom.xml). TestgenSupport.java giống hệt bản JUnit. Assert sai ném AssertionError, nên TestNG báo test đó là Fail. Chạy bằng mvn test.

selenium-js: cần selenium-webdriver và mocha (xem e2e/projects/selenium-js). testgen-support.js phải nằm cạnh file test. Chạy bằng npx mocha.

Biến môi trường mà test Selenium hiểu:

  • HEADED=1: hiện trình duyệt.
  • SELENIUM_REMOTE_URL=http://grid:4444: chạy trên Selenium Grid.
  • TESTGEN_TIMEOUT=15000: thời gian tự chờ, đơn vị ms, mặc định 10000.

Test của mọi target còn cần các biến secret của nó, ví dụ TEST_PASSWORD=... npx playwright test (xem mục Mật khẩu và dữ liệu nhạy cảm).

Code Selenium trông như thế nào

Selenium không có getByRole hay getByText, cũng không tự chờ element. Vì vậy test Selenium đi kèm một file hỗ trợ (TestgenSupport.java / testgen-support.js) để code sinh ra vẫn gọn và chạy ổn định:

page.navigate("http://127.0.0.1:4321/");
page.getByRole("textbox", "Email").fill("[email protected]");
page.getByLabel("Country").selectOption("jp");
page.getByRole("button", "Sign in").click();
page.getByTestId("welcome").shouldContainText("Welcome, [email protected] (jp)");
page.getByRole("listitem").filterHasText("Buy milk Delete").getByRole("button").click();
var page1 = page.waitForPopup(() -> page.getByRole("link", "Open help").click());

Bản JavaScript dùng đúng API của Playwright (page.getByRole('button', { name: 'Sign in' }), expect(...).toHaveText(...)). Thân test vì thế giống hệt bản playwright-ts, chỉ khác phần khởi tạo driver.

Bên dưới vẫn là Selenium WebDriver: các hành động dùng WebElement.click(), sendKeys(), Select, Actions. Biến driver vẫn có sẵn trong test nếu bạn cần viết thêm code Selenium thuần.

Log từng bước và test data-driven

Hai tùy chọn này tương ứng với ô Log steps và Data-driven (CSV) trong giao diện.

--log-steps (selenium-java, selenium-java-testng, selenium-js): test gọi page.setLogger(...). Mỗi hành động được ghi kèm giá trị thật (secret hiện ***), mỗi assert ghi passed hoặc FAILED. Bản TestNG ghi qua Reporter.log (vào report của TestNG và ra console), bản JUnit và bản JavaScript in ra console. Hai target Playwright không cần tùy chọn này: report và trace của Playwright đã có từng bước.

02:51:07.955 page.getByRole("textbox", "Email").fill("[email protected]")
02:51:08.121 page.getByTestId("welcome").shouldContainText("Welcome, [email protected] (vn)") passed
02:51:28.390 page.getByTestId("welcome").shouldContainText("Welcome, wrong (vn)") FAILED, received: Welcome, [email protected] (vn)

--data-driven (mọi target): giá trị đã gõ (fill), đã chọn (selectOption) và đã assert (shouldHaveText, shouldContainText, shouldHaveValue) trở thành cột của một file CSV. Test chạy một lần cho mỗi dòng, và mỗi dòng có kết quả Pass/Fail riêng. Giá trị secret không thành cột: chúng vẫn đọc từ biến môi trường.

@DataProvider(name = "loginData")
public static Object[][] loginData() {
    return TestgenSupport.readCsv(LoginTest.class, "LoginTest.csv", "email", "password", "expectedWelcome");
}

@Test(dataProvider = "loginData")
public void login(String email, String password, String expectedWelcome) {
    page.getByRole("textbox", "Email").fill(email);
    page.getByRole("textbox", "Password").fill(password);
    page.getByRole("button", "Sign in").click();
    page.getByTestId("welcome").shouldContainText(expectedWelcome);
}

Bản TypeScript, JavaScript và Go đọc từng dòng thành row:

const rows = readCsv(path.join(__dirname, 'login.csv'), ['email', 'expectedWelcome']);

for (const [index, row] of rows.entries()) {
  test(`login (row ${index + 1})`, async ({ page }) => {
    await page.getByRole('textbox', { name: 'Email' }).fill(row.email);
    await page.getByRole('textbox', { name: 'Password' }).fill(secret('TEST_PASSWORD'));
    await page.getByRole('button', { name: 'Sign in' }).click();
    await expect(page.getByTestId('welcome')).toContainText(row.expectedWelcome);
  });
}

| Target | File CSV | Mỗi dòng chạy thành | | --- | --- | --- | | selenium-java, selenium-java-testng | <Tên>Test.csv, resource cùng package với class test. Khi file test nằm trong src/test/java/<package>/, testgen ghi CSV vào src/test/resources/<package>/. | một lần chạy của @ParameterizedTest (JUnit) hoặc @Test(dataProvider = ...) (TestNG) | | playwright-ts | <tên>.csv cạnh file .spec.ts, kèm file hỗ trợ testgen-data.ts | một test <tên> (row N) | | selenium-js | <tên>.csv cạnh file .spec.js | một it <tên> (row N) | | playwright-go | testdata/<tên>.csv, kèm file hỗ trợ testgen_data_test.go | một subtest row_N (go test -run 'TestLogin/row_2') |

  • Dòng trong CSV: dòng đầu là tên cột, dòng thứ hai là giá trị lúc ghi. Thêm dòng để thêm bộ dữ liệu.
  • Không ghi đè: testgen không bao giờ ghi đè file CSV đã có (báo kept). Muốn sinh lại từ đầu thì xóa file đó.
  • Tên cột lấy theo ô nhập hoặc phần tử được assert: Email → email, placeholder Tìm kiếm sản phẩm... → timKiemSanPham, assert trên getByTestId("welcome") → expectedWelcome. Gõ cùng một giá trị nhiều lần thì dùng chung một cột.
  • Locator đi theo dữ liệu khi chữ trong locator bằng, hoặc chứa nguyên từ, một giá trị đã gõ trước đó. Ví dụ tìm "Những Kẻ Mê Sách" rồi bấm link "Những Kẻ Mê Sách": link dùng chung cột với từ khóa. Thêm việc "Buy milk" rồi bấm nút xóa của mục "Buy milk Delete": locator thành filterHasText(whatNeedsToBeDone + " Delete"). Giá trị ngắn hơn 3 ký tự chỉ được thay khi khớp nguyên văn, vì "1" có thể là một phần của bất kỳ số nào. Các chữ khác giữ nguyên như lúc ghi.
  • Assert là cột riêng: chữ mong đợi như Welcome, [email protected] (jp) là một cột riêng, kể cả khi nó chứa giá trị đã gõ. Mỗi dòng phải điền đúng chữ mong đợi của dòng đó.
  • Mở bằng Excel: file sinh ra có BOM để Excel hiện đúng tiếng Việt. Mọi target đọc được cả dấu phẩy lẫn dấu chấm phẩy (Excel ở một số ngôn ngữ lưu bằng ;), bỏ qua dòng trống và các cột thừa, ví dụ một cột note để mô tả từng dòng.

Cách hoạt động

 Bạn thao tác trên trình duyệt
        │  recorder của Playwright (như `playwright codegen --target=jsonl`, Inspector ẩn), ghi lại mỗi bước
        ▼
 recording.jsonl   ── danh sách bước, chưa gắn với ngôn ngữ nào (click, fill, assertText…)
        │  selector → chuỗi locator: dùng lại mã của Playwright (src/vendor/playwright)
        ▼
 Generators (src/generators/*)
   ├─ playwright-ts          giống `playwright codegen --target playwright-test`
   ├─ playwright-go          API playwright-go + require.NoError
   ├─ selenium-java          JUnit 5 + TestgenSupport.java
   ├─ selenium-java-testng   TestNG + TestgenSupport.java
   └─ selenium-js            Mocha + testgen-support.js
                                 └─ runtime/selenium/engine.js: chạy trong trang (executeScript)
                                    để tìm element theo role / text / label / test id… như Playwright
  • Recorder: dùng nguyên recorder của Playwright, nên cách chọn locator (ưu tiên test id, role + tên, label…) giống hệt Playwright codegen.
  • Engine locator cho Selenium: được kiểm tra bằng cách so kết quả với chính Playwright trên 70 locator mẫu (test/engine.test.ts).
  • Giữa bản ghi và generators: src/secrets.ts tìm các bước secret, src/generators/test-data.ts quyết định mỗi giá trị là chữ, cột CSV hay biến môi trường, src/checks.ts tạo cảnh báo. Giao diện sửa bản ghi bằng src/editing.ts (locator gõ tay được đổi lại thành selector bằng mã của Playwright) và chạy lại bằng src/replay.ts (playwright-core).

Giới hạn hiện tại

  • Định dạng JSONL là định dạng nội bộ của Playwright. Vì vậy playwright-core được ghim ở 1.63.0. Khi nâng cấp phải chạy lại npm test và npm run e2e (xem src/vendor/playwright/README.md).
  • Selenium không làm được các việc sau, nên chỗ đó sẽ có comment // TODO(testgen):
    • chờ download;
    • assert aria snapshot (thay bằng kiểm tra visible);
    • click chuột giữa;
    • giả lập thiết bị đầy đủ (chỉ áp dụng viewport và user agent);
    • colorScheme / timezone / geolocation;
    • lưu storage state.
  • getByRole trên Selenium chỉ gần đúng, vì thuật toán tính accessible name được đơn giản hóa. Khi gặp sai lệch, hãy thêm data-testid cho app, hoặc thêm trường hợp đó vào test/engine.test.ts.
  • Bản ghi không lưu đường dẫn đầy đủ của file upload, chỉ có tên file. Sửa bước đó trong giao diện (ô Files, mỗi dòng một đường dẫn) hoặc sửa setInputFiles(...) trong test. Replay bỏ qua bước upload khi không tìm thấy file.
  • Replay chạy bản ghi trên Playwright, không chạy code đã sinh. Replay qua không bảo đảm test Selenium qua, vì getByRole trên Selenium chỉ gần đúng (xem trên). Muốn chắc thì chạy chính test đã sinh, như npm run e2e làm với app demo.
  • Phát hiện secret dựa vào locator, không dựa vào type="password" của ô. Ô không có nhãn hay id gợi ý (ví dụ #field3) sẽ không được nhận ra: xem mục Checks và bật ô Secret trong giao diện. Ngược lại, ô có chữ "password" trong tên mà không phải mật khẩu thì tắt đi.
  • Report và trace của Playwright vẫn hiện giá trị gõ vào ô, kể cả giá trị đọc từ biến môi trường. Đừng chia sẻ chúng ra ngoài nếu test dùng mật khẩu thật.
  • --log-steps chỉ dành cho Selenium. Hai target Playwright bỏ qua tùy chọn này, CLI và giao diện có ghi chú khi bỏ qua.

Phát triển

Package trên npm chỉ chứa bản build. Để sửa code thì làm việc trên repo:

npm ci            # tự build (script prepare); gọi tool bằng `node dist/cli.js`
npm test          # vitest: so engine với Playwright, snapshot code sinh ra, test giao diện (cần Chrome)
npm run e2e       # sinh test cho mọi target rồi chạy trên app demo e2e/app (cần Go, JDK 17+ và Maven, Chrome)
E2E_TARGETS="selenium-js playwright-go" npm run e2e
  • Ghi lại bản demo: chạy app (e2e/app) ở cổng 4321, rồi chạy node e2e/record-demo.mjs. Script này điều khiển recorder thật của Playwright, không cần người thao tác.
  • Code sinh ra cho 2 bản ghi mẫu được lưu thành snapshot trong test/__snapshots__/. Đổi generator thì chạy npx vitest run -u và review diff.
  • Thêm target mới:
    1. Viết một Generator trong src/generators/ và đăng ký trong src/generators/index.ts.
    2. Thêm project mẫu trong e2e/projects/.
    3. Thêm bước chạy trong e2e/run.sh.
src/            CLI (cli.ts), đọc bản ghi, generators, server giao diện (ui/server.ts)
src/vendor/     mã lấy từ Playwright (Apache-2.0), xem NOTICE
runtime/        file hỗ trợ Selenium (Java, JS) + engine locator chạy trong trang
ui/             giao diện web (HTML/CSS/JS thuần, không cần build)
test/           unit test, fixture, snapshot
e2e/            app demo, bản ghi thật, project mẫu cho từng target, run.sh
scripts/        script phát hành lên npm (release.mjs)

Phát hành lên npm

GitLab CI (.gitlab-ci.yml) chạy test cho mọi lần push. Phát hành một version mới:

  1. Ghi thay đổi (bằng tiếng Anh) vào mục ## Unreleased ở đầu CHANGELOG.md rồi commit.
  2. Chạy npm run release -- patch (hoặc minor, major, 1.2.3). Script kiểm tra, chạy test, tăng version trong package.json và package-lock.json, đổi ## Unreleased thành ## <version> (<ngày>), commit chore: release <version>, rồi push main cùng tag v<version>.
  3. Pipeline của tag chạy test, rồi đưa version đó vào khu chờ duyệt của npm (npm stage publish).
  4. Duyệt bằng 2FA để version lên npm: npm stage list @xylentis/testgen rồi npm stage approve <stage-id>, hoặc bấm Approve ở tab Staged Packages trên trang package ở npmjs.com.
npm run release -- patch        # hoặc minor, major, 1.2.3
npm run release                 # phát hành version đang có trong package.json (CHANGELOG.md phải có mục `## <version>`)
npm run release -- --dry-run    # chạy thử: kiểm tra, test và `npm pack --dry-run`, không thay đổi gì

Trước khi tag, script kiểm tra: đang ở nhánh main, không có thay đổi chưa commit, không chậm hơn origin/main, version chưa có trên npm và mới hơn version latest, chưa có tag v<version>. npm run e2e cần Go, JDK và Maven nên không chạy trong script hay CI; hãy tự chạy khi đổi generator hay runtime Selenium.

Vì sao vẫn phải duyệt tay: npm chưa hỗ trợ trusted publishing (OIDC) cho GitLab tự host. Token bỏ qua 2FA cũng sẽ mất quyền publish thẳng từ tháng 1/2027. CI vì vậy chỉ dùng token "stage only": lộ token thì cũng không publish được.

Duyệt theo thứ tự version. Version được duyệt sẽ thành latest, kể cả khi đã có version mới hơn. Bản stage không dùng nữa thì xóa bằng npm stage reject <stage-id>.

Cài đặt một lần:

  1. Trên npmjs.com: Access Tokens → Generate New Token, quyền Read and write (stage only), chỉ cho package @xylentis/testgen. Token có quyền ghi hết hạn sau tối đa 90 ngày, nên cần tạo lại định kỳ.
  2. Lưu token vào biến CI/CD NPM_TOKEN của project, bật Masked và Protected: trong GitLab (Settings → CI/CD → Variables), hoặc copy token rồi chạy pbpaste | glab variable set NPM_TOKEN --masked --protected.
  3. Bảo vệ tag v* (Settings → Repository → Protected tags) để pipeline của tag nhận được biến protected: glab api -X POST projects/:id/protected_tags -f name='v*' -f create_access_level=40.

License

GNU AGPL-3.0-only, Copyright (C) 2026 Xylentis.

  • testgen: ai sửa testgen rồi phát hành lại, kể cả đem chạy thành dịch vụ qua mạng, đều phải công bố mã nguồn bản đã sửa theo AGPL.
  • Code sinh ra: test do testgen sinh ra và các file hỗ trợ đi kèm (TestgenSupport.java, testgen-support.js, engine) được dùng, sửa và phân phối tự do trong test của bạn, theo điều khoản bổ sung ở mục 7 của AGPL. Điều khoản này không cho phép dùng các file đó để làm một công cụ sinh code khác. Xem NOTICE.
  • Code lấy từ Playwright: các file trong src/vendor/playwright vẫn theo Apache License 2.0.