---
title: Selenium
sidebar:
  order: 2
---

{/* TODO: REVISE COPY TO V2 */}

import CommandTabs from '@components/CommandTabs.astro';

:::note

Make sure to go through the [prerequisites instructions] to be able to follow this guide.

:::

此 WebDriver 测试示例将使用 [Selenium][] 和一个流行的 Node.js 测试套装。 您应该已经安装 node.js ， 并且一起安装了 `npm` 或 `yarn` ， [已完成的示例项目][] 使用了 `pnpm`.

## 创建测试目录

让我们创建一个空目录用做测试项目。 在此示例项目中我们将使用嵌套目录，因为稍后还将使用其他框架，但通常您只需要使用一个框架。创建接下来我们需要使用的目录。 创建我们将要使用的目录 `mkdir -p e2e-tests` 。 指南接下来的部分我们假定您也在目录 `e2e-tests` 中完成。

## 初始化 Selenium 项目

我们将使用预设的 `package.json` 来引导此测试套件，因为我们已经选择了特定的要使用的依赖项，并希望展示一个简单的工作解决方案。 此部分的底部有一个折叠起来的内容，其中有关如何从头开始设置的指南。

`package.json`:

```json
{
  "name": "selenium",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "mocha"
  },
  "dependencies": {
    "chai": "^5.2.1",
    "mocha": "^11.7.1",
    "selenium-webdriver": "^4.34.0"
  }
}
```

我们有一个脚本将 [Mocha][] 作为测试框架运行，暴露在 `test` 命令中。 我们还有将用于运行测试的各种依赖项。 [Mocha][] 作为测试框架， [Chai][] 作为断言库， 和 [`selenium-webdriver`][] （Node.js 中的 [Selenium][] 软件包）。

<details>
<summary>如果你想看到如何从头开始设置一个项目，请点击我</summary>

如果你想从头安装依赖关系，只需运行以下命令。

<CommandTabs
  npm="npm install mocha chai selenium-webdriver"
  yarn="yarn add mocha chai selenium-webdriver"
/>

我建议将 `"test": "mocha"` 命令添加到 `package.json` 的 `"scripts"` 中，这样运行 Mocha 只需要执行

<CommandTabs npm="npm test" yarn="yarn test" />

</details>

## 测试

与 [WebdriverIO 测试套件](/zh-cn/develop/tests/webdriver/example/webdriverio/#config)不同，Selenium不是开箱即用的测试套件，需要开发人员来构建这些内容。 我们选择 [Mocha][], 它是相当中性的，与 WebDrivers 没有关系。 因此我们的脚本需要做一些工作才能以正确的顺序为我们设置所有内容。 [Mocha][] expects a testing file at `test/test.js` by default, so let's create that file now.

`test/test.js`:

```javascript
import os from 'os';
import path from 'path';
import { expect } from 'chai';
import { spawn, spawnSync } from 'child_process';
import { Builder, By, Capabilities } from 'selenium-webdriver';
import { fileURLToPath } from 'url';

const __dirname = fileURLToPath(new URL('.', import.meta.url));

// create the path to the expected application binary
const application = path.resolve(
  __dirname,
  '..',
  '..',
  'src-tauri',
  'target',
  'debug',
  'tauri-app'
);

// keep track of the webdriver instance we create
let driver;

// keep track of the tauri-driver process we start
let tauriDriver;
let exit = false;

before(async function () {
  // set timeout to 2 minutes to allow the program to build if it needs to
  this.timeout(120000);

  // ensure the app has been built
  spawnSync('yarn', ['tauri', 'build', '--debug', '--no-bundle'], {
    cwd: path.resolve(__dirname, '../..'),
    stdio: 'inherit',
    shell: true,
  });

  // start tauri-driver
  tauriDriver = spawn(
    path.resolve(os.homedir(), '.cargo', 'bin', 'tauri-driver'),
    [],
    { stdio: [null, process.stdout, process.stderr] }
  );
  tauriDriver.on('error', (error) => {
    console.error('tauri-driver error:', error);
    process.exit(1);
  });
  tauriDriver.on('exit', (code) => {
    if (!exit) {
      console.error('tauri-driver exited with code:', code);
      process.exit(1);
    }
  });

  const capabilities = new Capabilities();
  capabilities.set('tauri:options', { application });
  capabilities.setBrowserName('wry');

  // start the webdriver client
  driver = await new Builder()
    .withCapabilities(capabilities)
    .usingServer('http://127.0.0.1:4444/')
    .build();
});

after(async function () {
  // stop the webdriver session
  await closeTauriDriver();
});

describe('Hello Tauri', () => {
  it('should be cordial', async () => {
    const text = await driver.findElement(By.css('body > h1')).getText();
    expect(text).to.match(/^[hH]ello/);
  });

  it('should be excited', async () => {
    const text = await driver.findElement(By.css('body > h1')).getText();
    expect(text).to.match(/!$/);
  });

  it('should be easy on the eyes', async () => {
    // selenium returns color css values as rgb(r, g, b)
    const text = await driver
      .findElement(By.css('body'))
      .getCssValue('background-color');

    const rgb = text.match(/^rgb\((?<r>\d+), (?<g>\d+), (?<b>\d+)\)$/).groups;
    expect(rgb).to.have.all.keys('r', 'g', 'b');

    const luma = 0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b;
    expect(luma).to.be.lessThan(100);
  });
});

async function closeTauriDriver() {
  exit = true;
  // kill the tauri-driver process
  tauriDriver.kill();
  // stop the webdriver session
  await driver.quit();
}

function onShutdown(fn) {
  const cleanup = () => {
    try {
      fn();
    } finally {
      process.exit();
    }
  };

  process.on('exit', cleanup);
  process.on('SIGINT', cleanup);
  process.on('SIGTERM', cleanup);
  process.on('SIGHUP', cleanup);
  process.on('SIGBREAK', cleanup);
}

onShutdown(() => {
  closeTauriDriver();
});
```

If you are familiar with JS testing frameworks, `describe`, `it`, and `expect` should look familiar. We also have
semi-complex `before()` and `after()` callbacks to set up and teardown mocha. Lines that are not the tests themselves
have comments explaining the setup and teardown code. If you were familiar with the Spec file from the
[WebdriverIO example](/zh-cn/develop/tests/webdriver/example/webdriverio/#spec), you notice a lot more code that isn't tests, as we have to set up a few
more WebDriver related items.

## Running the Test Suite

Now that we are all set up with our dependencies and our test script, let's run it!

<CommandTabs npm="npm test" yarn="yarn test" />

We should see output the following output:

```text
➜  selenium git:(main) ✗ yarn test
yarn run v1.22.11
$ Mocha


  Hello Tauri
    ✔ should be cordial (120ms)
    ✔ should be excited
    ✔ should be easy on the eyes


  3 passing (588ms)

Done in 0.93s.
```

We can see that our `Hello Tauri` test suite we created with `describe` had all 3 items we created with `it` pass their
tests!

With [Selenium] and some hooking up to a test suite, we just enabled e2e testing without modifying our Tauri
application at all!

[prerequisites instructions]: /develop/tests/webdriver/
[Selenium]: https://selenium.dev/
[已完成的示例项目]: https://github.com/tauri-apps/webdriver-example
[Mocha]: https://mochajs.org/
[Chai]: https://www.chaijs.com/
[`selenium-webdriver`]: https://www.npmjs.com/package/selenium-webdriver
