docs(dotnet): recommend MSTest over NUnit (#31245)
This commit is contained in:
Родитель
664c4dd442
Коммит
f95b4e0ac8
|
@ -16,7 +16,7 @@ A few examples where it may come in handy:
|
|||
|
||||
All of that could be achieved via [APIRequestContext] methods.
|
||||
|
||||
The following examples rely on the [`Microsoft.Playwright.NUnit`](./test-runners.md) package which creates a Playwright and Page instance for each test.
|
||||
The following examples rely on the [`Microsoft.Playwright.MSTest`](./test-runners.md) package which creates a Playwright and Page instance for each test.
|
||||
|
||||
<!-- TOC -->
|
||||
|
||||
|
@ -34,22 +34,19 @@ The following example demonstrates how to use Playwright to test issues creation
|
|||
GitHub API requires authorization, so we'll configure the token once for all tests. While at it, we'll also set the `baseURL` to simplify the tests.
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Playwright.NUnit;
|
||||
using Microsoft.Playwright;
|
||||
using NUnit.Framework;
|
||||
using Microsoft.Playwright.MSTest;
|
||||
|
||||
namespace PlaywrightTests;
|
||||
|
||||
[TestClass]
|
||||
public class TestGitHubAPI : PlaywrightTest
|
||||
{
|
||||
static string API_TOKEN = Environment.GetEnvironmentVariable("GITHUB_API_TOKEN");
|
||||
static string? API_TOKEN = Environment.GetEnvironmentVariable("GITHUB_API_TOKEN");
|
||||
|
||||
private IAPIRequestContext Request = null;
|
||||
private IAPIRequestContext Request = null!;
|
||||
|
||||
[SetUp]
|
||||
[TestInitialize]
|
||||
public async Task SetUpAPITesting()
|
||||
{
|
||||
await CreateAPIRequestContext();
|
||||
|
@ -71,7 +68,7 @@ public class TestGitHubAPI : PlaywrightTest
|
|||
});
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
[TestCleanup]
|
||||
public async Task TearDownAPITesting()
|
||||
{
|
||||
await Request.DisposeAsync();
|
||||
|
@ -83,36 +80,34 @@ public class TestGitHubAPI : PlaywrightTest
|
|||
|
||||
Now that we initialized request object we can add a few tests that will create new issues in the repository.
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Playwright.NUnit;
|
||||
using Microsoft.Playwright;
|
||||
using NUnit.Framework;
|
||||
using Microsoft.Playwright.MSTest;
|
||||
|
||||
namespace PlaywrightTests;
|
||||
|
||||
[TestFixture]
|
||||
[TestClass]
|
||||
public class TestGitHubAPI : PlaywrightTest
|
||||
{
|
||||
static string REPO = "test-repo-2";
|
||||
static string REPO = "test";
|
||||
static string USER = Environment.GetEnvironmentVariable("GITHUB_USER");
|
||||
static string API_TOKEN = Environment.GetEnvironmentVariable("GITHUB_API_TOKEN");
|
||||
static string? API_TOKEN = Environment.GetEnvironmentVariable("GITHUB_API_TOKEN");
|
||||
|
||||
private IAPIRequestContext Request = null;
|
||||
private IAPIRequestContext Request = null!;
|
||||
|
||||
[Test]
|
||||
[TestMethod]
|
||||
public async Task ShouldCreateBugReport()
|
||||
{
|
||||
var data = new Dictionary<string, string>();
|
||||
data.Add("title", "[Bug] report 1");
|
||||
data.Add("body", "Bug description");
|
||||
var data = new Dictionary<string, string>
|
||||
{
|
||||
{ "title", "[Bug] report 1" },
|
||||
{ "body", "Bug description" }
|
||||
};
|
||||
var newIssue = await Request.PostAsync("/repos/" + USER + "/" + REPO + "/issues", new() { DataObject = data });
|
||||
Assert.True(newIssue.Ok);
|
||||
await Expect(newIssue).ToBeOKAsync();
|
||||
|
||||
var issues = await Request.GetAsync("/repos/" + USER + "/" + REPO + "/issues");
|
||||
Assert.True(issues.Ok);
|
||||
await Expect(newIssue).ToBeOKAsync();
|
||||
var issuesJsonResponse = await issues.JsonAsync();
|
||||
JsonElement? issue = null;
|
||||
foreach (JsonElement issueObj in issuesJsonResponse?.EnumerateArray())
|
||||
|
@ -125,23 +120,24 @@ public class TestGitHubAPI : PlaywrightTest
|
|||
}
|
||||
}
|
||||
}
|
||||
Assert.NotNull(issue);
|
||||
Assert.IsNotNull(issue);
|
||||
Assert.AreEqual("Bug description", issue?.GetProperty("body").GetString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestMethod]
|
||||
public async Task ShouldCreateFeatureRequests()
|
||||
{
|
||||
var data = new Dictionary<string, string>();
|
||||
data.Add("title", "[Feature] request 1");
|
||||
data.Add("body", "Feature description");
|
||||
var data = new Dictionary<string, string>
|
||||
{
|
||||
{ "title", "[Feature] request 1" },
|
||||
{ "body", "Feature description" }
|
||||
};
|
||||
var newIssue = await Request.PostAsync("/repos/" + USER + "/" + REPO + "/issues", new() { DataObject = data });
|
||||
Assert.True(newIssue.Ok);
|
||||
await Expect(newIssue).ToBeOKAsync();
|
||||
|
||||
var issues = await Request.GetAsync("/repos/" + USER + "/" + REPO + "/issues");
|
||||
Assert.True(issues.Ok);
|
||||
await Expect(newIssue).ToBeOKAsync();
|
||||
var issuesJsonResponse = await issues.JsonAsync();
|
||||
var issuesJson = (await issues.JsonAsync())?.EnumerateArray();
|
||||
|
||||
JsonElement? issue = null;
|
||||
foreach (JsonElement issueObj in issuesJsonResponse?.EnumerateArray())
|
||||
|
@ -154,7 +150,7 @@ public class TestGitHubAPI : PlaywrightTest
|
|||
}
|
||||
}
|
||||
}
|
||||
Assert.NotNull(issue);
|
||||
Assert.IsNotNull(issue);
|
||||
Assert.AreEqual("Feature description", issue?.GetProperty("body").GetString());
|
||||
}
|
||||
|
||||
|
@ -167,11 +163,17 @@ public class TestGitHubAPI : PlaywrightTest
|
|||
These tests assume that repository exists. You probably want to create a new one before running tests and delete it afterwards. Use `[SetUp]` and `[TearDown]` hooks for that.
|
||||
|
||||
```csharp
|
||||
using System.Text.Json;
|
||||
using Microsoft.Playwright;
|
||||
using Microsoft.Playwright.MSTest;
|
||||
|
||||
namespace PlaywrightTests;
|
||||
|
||||
[TestClass]
|
||||
public class TestGitHubAPI : PlaywrightTest
|
||||
{
|
||||
// ...
|
||||
|
||||
[SetUp]
|
||||
[TestInitialize]
|
||||
public async Task SetUpAPITesting()
|
||||
{
|
||||
await CreateAPIRequestContext();
|
||||
|
@ -187,10 +189,10 @@ public class TestGitHubAPI : PlaywrightTest
|
|||
["name"] = REPO,
|
||||
},
|
||||
});
|
||||
Assert.True(resp.Ok);
|
||||
await Expect(resp).ToBeOKAsync();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
[TestCleanup]
|
||||
public async Task TearDownAPITesting()
|
||||
{
|
||||
await DeleteTestRepository();
|
||||
|
@ -200,7 +202,7 @@ public class TestGitHubAPI : PlaywrightTest
|
|||
private async Task DeleteTestRepository()
|
||||
{
|
||||
var resp = await Request.DeleteAsync("/repos/" + USER + "/" + REPO);
|
||||
Assert.True(resp.Ok);
|
||||
await Expect(resp).ToBeOKAsync();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -210,36 +212,34 @@ public class TestGitHubAPI : PlaywrightTest
|
|||
Here is the complete example of an API test:
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Playwright.NUnit;
|
||||
using Microsoft.Playwright;
|
||||
using NUnit.Framework;
|
||||
using Microsoft.Playwright.MSTest;
|
||||
|
||||
namespace PlaywrightTests;
|
||||
|
||||
[TestFixture]
|
||||
[TestClass]
|
||||
public class TestGitHubAPI : PlaywrightTest
|
||||
{
|
||||
static string REPO = "test-repo-2";
|
||||
static string USER = Environment.GetEnvironmentVariable("GITHUB_USER");
|
||||
static string API_TOKEN = Environment.GetEnvironmentVariable("GITHUB_API_TOKEN");
|
||||
static string? API_TOKEN = Environment.GetEnvironmentVariable("GITHUB_API_TOKEN");
|
||||
|
||||
private IAPIRequestContext Request = null;
|
||||
private IAPIRequestContext Request = null!;
|
||||
|
||||
[Test]
|
||||
[TestMethod]
|
||||
public async Task ShouldCreateBugReport()
|
||||
{
|
||||
var data = new Dictionary<string, string>();
|
||||
data.Add("title", "[Bug] report 1");
|
||||
data.Add("body", "Bug description");
|
||||
var data = new Dictionary<string, string>
|
||||
{
|
||||
{ "title", "[Bug] report 1" },
|
||||
{ "body", "Bug description" }
|
||||
};
|
||||
var newIssue = await Request.PostAsync("/repos/" + USER + "/" + REPO + "/issues", new() { DataObject = data });
|
||||
Assert.True(newIssue.Ok);
|
||||
await Expect(newIssue).ToBeOKAsync();
|
||||
|
||||
var issues = await Request.GetAsync("/repos/" + USER + "/" + REPO + "/issues");
|
||||
Assert.True(issues.Ok);
|
||||
await Expect(newIssue).ToBeOKAsync();
|
||||
var issuesJsonResponse = await issues.JsonAsync();
|
||||
JsonElement? issue = null;
|
||||
foreach (JsonElement issueObj in issuesJsonResponse?.EnumerateArray())
|
||||
|
@ -252,23 +252,24 @@ public class TestGitHubAPI : PlaywrightTest
|
|||
}
|
||||
}
|
||||
}
|
||||
Assert.NotNull(issue);
|
||||
Assert.IsNotNull(issue);
|
||||
Assert.AreEqual("Bug description", issue?.GetProperty("body").GetString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestMethod]
|
||||
public async Task ShouldCreateFeatureRequests()
|
||||
{
|
||||
var data = new Dictionary<string, string>();
|
||||
data.Add("title", "[Feature] request 1");
|
||||
data.Add("body", "Feature description");
|
||||
var data = new Dictionary<string, string>
|
||||
{
|
||||
{ "title", "[Feature] request 1" },
|
||||
{ "body", "Feature description" }
|
||||
};
|
||||
var newIssue = await Request.PostAsync("/repos/" + USER + "/" + REPO + "/issues", new() { DataObject = data });
|
||||
Assert.True(newIssue.Ok);
|
||||
await Expect(newIssue).ToBeOKAsync();
|
||||
|
||||
var issues = await Request.GetAsync("/repos/" + USER + "/" + REPO + "/issues");
|
||||
Assert.True(issues.Ok);
|
||||
await Expect(newIssue).ToBeOKAsync();
|
||||
var issuesJsonResponse = await issues.JsonAsync();
|
||||
var issuesJson = (await issues.JsonAsync())?.EnumerateArray();
|
||||
|
||||
JsonElement? issue = null;
|
||||
foreach (JsonElement issueObj in issuesJsonResponse?.EnumerateArray())
|
||||
|
@ -281,11 +282,11 @@ public class TestGitHubAPI : PlaywrightTest
|
|||
}
|
||||
}
|
||||
}
|
||||
Assert.NotNull(issue);
|
||||
Assert.IsNotNull(issue);
|
||||
Assert.AreEqual("Feature description", issue?.GetProperty("body").GetString());
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
[TestInitialize]
|
||||
public async Task SetUpAPITesting()
|
||||
{
|
||||
await CreateAPIRequestContext();
|
||||
|
@ -294,14 +295,16 @@ public class TestGitHubAPI : PlaywrightTest
|
|||
|
||||
private async Task CreateAPIRequestContext()
|
||||
{
|
||||
var headers = new Dictionary<string, string>();
|
||||
var headers = new Dictionary<string, string>
|
||||
{
|
||||
// We set this header per GitHub guidelines.
|
||||
headers.Add("Accept", "application/vnd.github.v3+json");
|
||||
{ "Accept", "application/vnd.github.v3+json" },
|
||||
// Add authorization token to all requests.
|
||||
// Assuming personal access token available in the environment.
|
||||
headers.Add("Authorization", "token " + API_TOKEN);
|
||||
{ "Authorization", "token " + API_TOKEN }
|
||||
};
|
||||
|
||||
Request = await this.Playwright.APIRequest.NewContextAsync(new()
|
||||
Request = await Playwright.APIRequest.NewContextAsync(new()
|
||||
{
|
||||
// All requests we send go to this API endpoint.
|
||||
BaseURL = "https://api.github.com",
|
||||
|
@ -318,10 +321,10 @@ public class TestGitHubAPI : PlaywrightTest
|
|||
["name"] = REPO,
|
||||
},
|
||||
});
|
||||
Assert.True(resp.Ok);
|
||||
await Expect(resp).ToBeOKAsync();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
[TestCleanup]
|
||||
public async Task TearDownAPITesting()
|
||||
{
|
||||
await DeleteTestRepository();
|
||||
|
@ -331,7 +334,7 @@ public class TestGitHubAPI : PlaywrightTest
|
|||
private async Task DeleteTestRepository()
|
||||
{
|
||||
var resp = await Request.DeleteAsync("/repos/" + USER + "/" + REPO);
|
||||
Assert.True(resp.Ok);
|
||||
await Expect(resp).ToBeOKAsync();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -344,14 +347,16 @@ project to check that it appears at the top of the list. The check is performed
|
|||
```csharp
|
||||
class TestGitHubAPI : PageTest
|
||||
{
|
||||
[Test]
|
||||
[TestMethod]
|
||||
public async Task LastCreatedIssueShouldBeFirstInTheList()
|
||||
{
|
||||
var data = new Dictionary<string, string>();
|
||||
data.Add("title", "[Feature] request 1");
|
||||
data.Add("body", "Feature description");
|
||||
var data = new Dictionary<string, string>
|
||||
{
|
||||
{ "title", "[Feature] request 1" },
|
||||
{ "body", "Feature description" }
|
||||
};
|
||||
var newIssue = await Request.PostAsync("/repos/" + USER + "/" + REPO + "/issues", new() { DataObject = data });
|
||||
Assert.True(newIssue.Ok);
|
||||
await Expect(newIssue).ToBeOKAsync();
|
||||
|
||||
// When inheriting from 'PlaywrightTest' it only gives you a Playwright instance. To get a Page instance, either start
|
||||
// a browser, context, and page manually or inherit from 'PageTest' which will launch it for you.
|
||||
|
@ -368,9 +373,10 @@ The following test creates a new issue via user interface in the browser and the
|
|||
it was created:
|
||||
|
||||
```csharp
|
||||
// Make sure to extend from PageTest if you want to use the Page class.
|
||||
class GitHubTests : PageTest
|
||||
{
|
||||
[Test]
|
||||
[TestMethod]
|
||||
public async Task LastCreatedIssueShouldBeOnTheServer()
|
||||
{
|
||||
await Page.GotoAsync("https://github.com/" + USER + "/" + REPO + "/issues");
|
||||
|
@ -378,10 +384,10 @@ class GitHubTests : PageTest
|
|||
await Page.Locator("[aria-label='Title']").FillAsync("Bug report 1");
|
||||
await Page.Locator("[aria-label='Comment body']").FillAsync("Bug description");
|
||||
await Page.Locator("text=Submit new issue").ClickAsync();
|
||||
String issueId = Page.Url.Substring(Page.Url.LastIndexOf('/'));
|
||||
var issueId = Page.Url.Substring(Page.Url.LastIndexOf('/'));
|
||||
|
||||
var newIssue = await Request.GetAsync("https://github.com/" + USER + "/" + REPO + "/issues/" + issueId);
|
||||
Assert.True(newIssue.Ok);
|
||||
await Expect(newIssue).ToBeOKAsync();
|
||||
StringAssert.Contains(await newIssue.TextAsync(), "Bug report 1");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,21 +47,19 @@ def test_status_becomes_submitted(page: Page) -> None:
|
|||
```
|
||||
|
||||
```csharp
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Playwright.NUnit;
|
||||
using NUnit.Framework;
|
||||
using Microsoft.Playwright;
|
||||
using Microsoft.Playwright.MSTest;
|
||||
|
||||
namespace PlaywrightTests;
|
||||
|
||||
[TestFixture]
|
||||
[TestClass]
|
||||
public class ExampleTests : PageTest
|
||||
{
|
||||
[Test]
|
||||
[TestMethod]
|
||||
public async Task StatusBecomesSubmitted()
|
||||
{
|
||||
// ..
|
||||
await Page.GetByRole(AriaRole.Button).ClickAsync();
|
||||
// ...
|
||||
await Page.GetByRole(AriaRole.Button, new() { Name = "Sign In" }).ClickAsync();
|
||||
await Expect(Page.Locator(".status")).ToHaveTextAsync("Submitted");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,21 +50,19 @@ def test_navigates_to_login_page(page: Page) -> None:
|
|||
|
||||
```csharp
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Playwright.NUnit;
|
||||
using NUnit.Framework;
|
||||
using Microsoft.Playwright;
|
||||
using Microsoft.Playwright.MSTest;
|
||||
|
||||
namespace PlaywrightTests;
|
||||
|
||||
[TestFixture]
|
||||
[TestClass]
|
||||
public class ExampleTests : PageTest
|
||||
{
|
||||
[Test]
|
||||
[TestMethod]
|
||||
public async Task NavigatetoLoginPage()
|
||||
{
|
||||
// ..
|
||||
await Page.GetByText("Sing in").ClickAsync();
|
||||
await Expect(Page.Locator("div#foobar")).ToHaveURL(new Regex(".*/login"));
|
||||
await Page.GetByRole(AriaRole.Button, new() { Name = "Sign In" }).ClickAsync();
|
||||
await Expect(Page).ToHaveURLAsync(new Regex(".*/login"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -50,19 +50,18 @@ public class TestExample {
|
|||
```
|
||||
|
||||
```csharp
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Playwright.NUnit;
|
||||
using NUnit.Framework;
|
||||
using Microsoft.Playwright;
|
||||
using Microsoft.Playwright.MSTest;
|
||||
|
||||
namespace PlaywrightTests;
|
||||
|
||||
[TestFixture]
|
||||
[TestClass]
|
||||
public class ExampleTests : PageTest
|
||||
{
|
||||
[Test]
|
||||
[TestMethod]
|
||||
public async Task StatusBecomesSubmitted()
|
||||
{
|
||||
await Page.Locator("#submit-button").ClickAsync();
|
||||
await Page.GetByRole(AriaRole.Button, new() { Name = "Submit" }).ClickAsync();
|
||||
await Expect(Page.Locator(".status")).ToHaveTextAsync("Submitted");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,16 +7,16 @@ title: "Installation"
|
|||
|
||||
Playwright was created specifically to accommodate the needs of end-to-end testing. Playwright supports all modern rendering engines including Chromium, WebKit, and Firefox. Test on Windows, Linux, and macOS, locally or on CI, headless or headed with native mobile emulation.
|
||||
|
||||
You can choose to use [NUnit base classes](./test-runners.md#nunit) or [MSTest base classes](./test-runners.md#mstest) that Playwright provides to write end-to-end tests. These classes support running tests on multiple browser engines, parallelizing tests, adjusting launch/context options and getting a [Page]/[BrowserContext] instance per test out of the box. Alternatively you can use the [library](./library.md) to manually write the testing infrastructure.
|
||||
You can choose to use [MSTest base classes](./test-runners.md#mstest) or [NUnit base classes](./test-runners.md#nunit) that Playwright provides to write end-to-end tests. These classes support running tests on multiple browser engines, parallelizing tests, adjusting launch/context options and getting a [Page]/[BrowserContext] instance per test out of the box. Alternatively you can use the [library](./library.md) to manually write the testing infrastructure.
|
||||
|
||||
1. Start by creating a new project with `dotnet new`. This will create the `PlaywrightTests` directory which includes a `UnitTest1.cs` file:
|
||||
|
||||
<Tabs
|
||||
groupId="test-runners"
|
||||
defaultValue="nunit"
|
||||
defaultValue="mstest"
|
||||
values={[
|
||||
{label: 'MSTest', value: 'mstest'},
|
||||
{label: 'NUnit', value: 'nunit'},
|
||||
{label: 'MSTest', value: 'mstest'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="nunit">
|
||||
|
@ -41,10 +41,10 @@ cd PlaywrightTests
|
|||
|
||||
<Tabs
|
||||
groupId="test-runners"
|
||||
defaultValue="nunit"
|
||||
defaultValue="mstest"
|
||||
values={[
|
||||
{label: 'MSTest', value: 'mstest'},
|
||||
{label: 'NUnit', value: 'nunit'},
|
||||
{label: 'MSTest', value: 'mstest'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="nunit">
|
||||
|
@ -83,10 +83,10 @@ Edit the `UnitTest1.cs` file with the code below to create an example end-to-end
|
|||
|
||||
<Tabs
|
||||
groupId="test-runners"
|
||||
defaultValue="nunit"
|
||||
defaultValue="mstest"
|
||||
values={[
|
||||
{label: 'MSTest', value: 'mstest'},
|
||||
{label: 'NUnit', value: 'nunit'},
|
||||
{label: 'MSTest', value: 'mstest'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="nunit">
|
||||
|
@ -132,10 +132,8 @@ public class ExampleTest : PageTest
|
|||
|
||||
```csharp title="UnitTest1.cs"
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Playwright;
|
||||
using Microsoft.Playwright.MSTest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace PlaywrightTests;
|
||||
|
||||
|
@ -192,4 +190,4 @@ See our doc on [Running and Debugging Tests](./running-tests.md) to learn more a
|
|||
- [Generate tests with Codegen](./codegen-intro.md)
|
||||
- [See a trace of your tests](./trace-viewer-intro.md)
|
||||
- [Run tests on CI](./ci-intro.md)
|
||||
- [Learn more about the NUnit and MSTest base classes](./test-runners.md)
|
||||
- [Learn more about the MSTest and NUnit base classes](./test-runners.md)
|
||||
|
|
|
@ -30,7 +30,7 @@ You can choose any testing framework such as JUnit or TestNG based on your proje
|
|||
|
||||
## .NET
|
||||
|
||||
Playwright for .NET comes with [NUnit base classes](https://playwright.dev/dotnet/docs/test-runners#nunit) and [MSTest base classes](https://playwright.dev/dotnet/docs/test-runners#mstest) for writing end-to-end tests.
|
||||
Playwright for .NET comes with [MSTest base classes](https://playwright.dev/dotnet/docs/test-runners#mstest) and [NUnit base classes](https://playwright.dev/dotnet/docs/test-runners#nunit) for writing end-to-end tests.
|
||||
|
||||
* [Documentation](https://playwright.dev/dotnet/docs/intro)
|
||||
* [GitHub repo](https://github.com/microsoft/playwright-dotnet)
|
||||
|
|
|
@ -5,7 +5,7 @@ title: "Getting started - Library"
|
|||
|
||||
## Introduction
|
||||
|
||||
Playwright can either be used with the [NUnit](./test-runners.md#nunit) or [MSTest](./test-runners.md#mstest), or as a Playwright Library (this guide). If you are working on an application that utilizes Playwright capabilities or you are using Playwright with another test runner, read on.
|
||||
Playwright can either be used with the [MSTest](./test-runners.md#mstest) or [NUnit](./test-runners.md#nunit), or as a Playwright Library (this guide). If you are working on an application that utilizes Playwright capabilities or you are using Playwright with another test runner, read on.
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
|
@ -637,7 +637,7 @@ This version was also tested against the following stable channels:
|
|||
### Other highlights
|
||||
|
||||
- New option `MaxRedirects` for [`method: APIRequestContext.get`] and others to limit redirect count.
|
||||
- Codegen now supports NUnit and MSTest frameworks.
|
||||
- Codegen now supports MSTest and NUnit frameworks.
|
||||
- ASP .NET is now supported.
|
||||
|
||||
### Behavior Change
|
||||
|
|
|
@ -109,10 +109,10 @@ dotnet test --filter "Name~GetStartedLink"
|
|||
|
||||
<Tabs
|
||||
groupId="test-runners"
|
||||
defaultValue="nunit"
|
||||
defaultValue="mstest"
|
||||
values={[
|
||||
{label: 'MSTest', value: 'mstest'},
|
||||
{label: 'NUnit', value: 'nunit'},
|
||||
{label: 'MSTest', value: 'mstest'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="nunit">
|
||||
|
@ -159,4 +159,4 @@ Check out our [debugging guide](./debug.md) to learn more about the [Playwright
|
|||
- [Generate tests with Codegen](./codegen-intro.md)
|
||||
- [See a trace of your tests](./trace-viewer-intro.md)
|
||||
- [Run tests on CI](./ci-intro.md)
|
||||
- [Learn more about the NUnit and MSTest base classes](./test-runners.md)
|
||||
- [Learn more about the MSTest and NUnit base classes](./test-runners.md)
|
||||
|
|
|
@ -77,10 +77,10 @@ expect.set_options(timeout=10_000)
|
|||
|
||||
<Tabs
|
||||
groupId="test-runners"
|
||||
defaultValue="nunit"
|
||||
defaultValue="mstest"
|
||||
values={[
|
||||
{label: 'MSTest', value: 'mstest'},
|
||||
{label: 'NUnit', value: 'nunit'},
|
||||
{label: 'MSTest', value: 'mstest'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="nunit">
|
||||
|
|
|
@ -5,135 +5,12 @@ title: "Test Runners"
|
|||
|
||||
## Introduction
|
||||
|
||||
While Playwright for .NET isn't tied to a particular test runner or testing framework, in our experience it works best with the built-in .NET test runner, and using NUnit as the test framework. NUnit is also what we use internally for [our tests](https://github.com/microsoft/playwright-dotnet/tree/main/src/Playwright.Tests).
|
||||
While Playwright for .NET isn't tied to a particular test runner or testing framework, in our experience the easiest way of getting started is by using the base classes we provide for [MSTest](#mstest) and [NUnit](#nunit). These classes support running tests on multiple browser engines, adjusting launch/context options and getting a [Page]/[BrowserContext] instance per test out of the box.
|
||||
|
||||
Playwright and Browser instances can be reused between tests for better performance. We
|
||||
Playwright and Browser instances will be reused between tests for better performance. We
|
||||
recommend running each test case in a new BrowserContext, this way browser state will be
|
||||
isolated between the tests.
|
||||
|
||||
|
||||
## NUnit
|
||||
|
||||
Playwright provides base classes to write tests with NUnit via the [`Microsoft.Playwright.NUnit`](https://www.nuget.org/packages/Microsoft.Playwright.NUnit) package.
|
||||
|
||||
Check out the [installation guide](./intro.md) to get started.
|
||||
|
||||
### Running NUnit tests in Parallel
|
||||
|
||||
By default NUnit will run all test files in parallel, while running tests inside each file sequentially (`ParallelScope.Self`). It will create as many processes as there are cores on the host system. You can adjust this behavior using the NUnit.NumberOfTestWorkers parameter.
|
||||
Only `ParallelScope.Self` is supported.
|
||||
|
||||
For CPU-bound tests, we recommend using as many workers as there are cores on your system, divided by 2. For IO-bound tests you can use as many workers as you have cores.
|
||||
|
||||
```bash
|
||||
dotnet test -- NUnit.NumberOfTestWorkers=5
|
||||
```
|
||||
|
||||
### Customizing [BrowserContext] options
|
||||
|
||||
To customize context options, you can override the `ContextOptions` method of your test class derived from `Microsoft.Playwright.MSTest.PageTest` or `Microsoft.Playwright.MSTest.ContextTest`. See the following example:
|
||||
|
||||
```csharp
|
||||
using Microsoft.Playwright.NUnit;
|
||||
|
||||
namespace PlaywrightTests;
|
||||
|
||||
[Parallelizable(ParallelScope.Self)]
|
||||
[TestFixture]
|
||||
public class MyTest : PageTest
|
||||
{
|
||||
[Test]
|
||||
public async Task TestWithCustomContextOptions()
|
||||
{
|
||||
// The following Page (and BrowserContext) instance has the custom colorScheme, viewport and baseURL set:
|
||||
await Page.GotoAsync("/login");
|
||||
}
|
||||
|
||||
public override BrowserNewContextOptions ContextOptions()
|
||||
{
|
||||
return new BrowserNewContextOptions()
|
||||
{
|
||||
ColorScheme = ColorScheme.Light,
|
||||
ViewportSize = new()
|
||||
{
|
||||
Width = 1920,
|
||||
Height = 1080
|
||||
},
|
||||
BaseURL = "https://github.com",
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Customizing [Browser]/launch options
|
||||
|
||||
[Browser]/launch options can be overridden either using a run settings file or by setting the run settings options directly via the
|
||||
CLI. See the following example:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RunSettings>
|
||||
<Playwright>
|
||||
<BrowserName>chromium</BrowserName>
|
||||
<LaunchOptions>
|
||||
<Headless>false</Headless>
|
||||
<Channel>msedge</Channel>
|
||||
</LaunchOptions>
|
||||
</Playwright>
|
||||
</RunSettings>
|
||||
```
|
||||
|
||||
```bash
|
||||
dotnet test -- Playwright.BrowserName=chromium Playwright.LaunchOptions.Headless=false Playwright.LaunchOptions.Channel=msedge
|
||||
```
|
||||
|
||||
### Using Verbose API Logs
|
||||
|
||||
When you have enabled the [verbose API log](./debug.md#verbose-api-logs), via the `DEBUG` environment variable, you will see the messages in the standard error stream. In NUnit, within Visual Studio, that will be the `Tests` pane of the `Output` window. It will also be displayed in the `Test Log` for each test.
|
||||
|
||||
### Using the .runsettings file
|
||||
|
||||
When running tests from Visual Studio, you can take advantage of the `.runsettings` file. The following shows a reference of the supported values.
|
||||
|
||||
For example, to specify the amount of workers you can use `NUnit.NumberOfTestWorkers` or to enable `DEBUG` logs `RunConfiguration.EnvironmentVariables`.
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RunSettings>
|
||||
<!-- NUnit adapter -->
|
||||
<NUnit>
|
||||
<NumberOfTestWorkers>24</NumberOfTestWorkers>
|
||||
</NUnit>
|
||||
<!-- General run configuration -->
|
||||
<RunConfiguration>
|
||||
<EnvironmentVariables>
|
||||
<!-- For debugging selectors, it's recommend to set the following environment variable -->
|
||||
<DEBUG>pw:api</DEBUG>
|
||||
</EnvironmentVariables>
|
||||
</RunConfiguration>
|
||||
<!-- Playwright -->
|
||||
<Playwright>
|
||||
<BrowserName>chromium</BrowserName>
|
||||
<ExpectTimeout>5000</ExpectTimeout>
|
||||
<LaunchOptions>
|
||||
<Headless>false</Headless>
|
||||
<Channel>msedge</Channel>
|
||||
</LaunchOptions>
|
||||
</Playwright>
|
||||
</RunSettings>
|
||||
```
|
||||
|
||||
### Base NUnit classes for Playwright
|
||||
|
||||
There are a few base classes available to you in `Microsoft.Playwright.NUnit` namespace:
|
||||
|
||||
|Test |Description|
|
||||
|--------------|-----------|
|
||||
|PageTest |Each test gets a fresh copy of a web [Page] created in its own unique [BrowserContext]. Extending this class is the simplest way of writing a fully-functional Playwright test.<br></br><br></br>Note: You can override the `ContextOptions` method in each test file to control context options, the ones typically passed into the [`method: Browser.newContext`] method. That way you can specify all kinds of emulation options for your test file individually.|
|
||||
|ContextTest |Each test will get a fresh copy of a [BrowserContext]. You can create as many pages in this context as you'd like. Using this test is the easiest way to test multi-page scenarios where you need more than one tab.<br></br><br></br>Note: You can override the `ContextOptions` method in each test file to control context options, the ones typically passed into the [`method: Browser.newContext`] method. That way you can specify all kinds of emulation options for your test file individually.|
|
||||
|BrowserTest |Each test will get a browser and can create as many contexts as it likes. Each test is responsible for cleaning up all the contexts it created.|
|
||||
|PlaywrightTest|This gives each test a Playwright object so that the test could start and stop as many browsers as it likes.|
|
||||
|
||||
## MSTest
|
||||
|
||||
Playwright provides base classes to write tests with MSTest via the [`Microsoft.Playwright.MSTest`](https://www.nuget.org/packages/Microsoft.Playwright.MSTest) package.
|
||||
|
@ -259,6 +136,128 @@ There are a few base classes available to you in `Microsoft.Playwright.MSTest` n
|
|||
|BrowserTest |Each test will get a browser and can create as many contexts as it likes. Each test is responsible for cleaning up all the contexts it created.|
|
||||
|PlaywrightTest|This gives each test a Playwright object so that the test could start and stop as many browsers as it likes.|
|
||||
|
||||
## NUnit
|
||||
|
||||
Playwright provides base classes to write tests with NUnit via the [`Microsoft.Playwright.NUnit`](https://www.nuget.org/packages/Microsoft.Playwright.NUnit) package.
|
||||
|
||||
Check out the [installation guide](./intro.md) to get started.
|
||||
|
||||
### Running NUnit tests in Parallel
|
||||
|
||||
By default NUnit will run all test files in parallel, while running tests inside each file sequentially (`ParallelScope.Self`). It will create as many processes as there are cores on the host system. You can adjust this behavior using the NUnit.NumberOfTestWorkers parameter.
|
||||
Only `ParallelScope.Self` is supported.
|
||||
|
||||
For CPU-bound tests, we recommend using as many workers as there are cores on your system, divided by 2. For IO-bound tests you can use as many workers as you have cores.
|
||||
|
||||
```bash
|
||||
dotnet test -- NUnit.NumberOfTestWorkers=5
|
||||
```
|
||||
|
||||
### Customizing [BrowserContext] options
|
||||
|
||||
To customize context options, you can override the `ContextOptions` method of your test class derived from `Microsoft.Playwright.MSTest.PageTest` or `Microsoft.Playwright.MSTest.ContextTest`. See the following example:
|
||||
|
||||
```csharp
|
||||
using Microsoft.Playwright.NUnit;
|
||||
|
||||
namespace PlaywrightTests;
|
||||
|
||||
[Parallelizable(ParallelScope.Self)]
|
||||
[TestFixture]
|
||||
public class MyTest : PageTest
|
||||
{
|
||||
[Test]
|
||||
public async Task TestWithCustomContextOptions()
|
||||
{
|
||||
// The following Page (and BrowserContext) instance has the custom colorScheme, viewport and baseURL set:
|
||||
await Page.GotoAsync("/login");
|
||||
}
|
||||
|
||||
public override BrowserNewContextOptions ContextOptions()
|
||||
{
|
||||
return new BrowserNewContextOptions()
|
||||
{
|
||||
ColorScheme = ColorScheme.Light,
|
||||
ViewportSize = new()
|
||||
{
|
||||
Width = 1920,
|
||||
Height = 1080
|
||||
},
|
||||
BaseURL = "https://github.com",
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Customizing [Browser]/launch options
|
||||
|
||||
[Browser]/launch options can be overridden either using a run settings file or by setting the run settings options directly via the
|
||||
CLI. See the following example:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RunSettings>
|
||||
<Playwright>
|
||||
<BrowserName>chromium</BrowserName>
|
||||
<LaunchOptions>
|
||||
<Headless>false</Headless>
|
||||
<Channel>msedge</Channel>
|
||||
</LaunchOptions>
|
||||
</Playwright>
|
||||
</RunSettings>
|
||||
```
|
||||
|
||||
```bash
|
||||
dotnet test -- Playwright.BrowserName=chromium Playwright.LaunchOptions.Headless=false Playwright.LaunchOptions.Channel=msedge
|
||||
```
|
||||
|
||||
### Using Verbose API Logs
|
||||
|
||||
When you have enabled the [verbose API log](./debug.md#verbose-api-logs), via the `DEBUG` environment variable, you will see the messages in the standard error stream. In NUnit, within Visual Studio, that will be the `Tests` pane of the `Output` window. It will also be displayed in the `Test Log` for each test.
|
||||
|
||||
### Using the .runsettings file
|
||||
|
||||
When running tests from Visual Studio, you can take advantage of the `.runsettings` file. The following shows a reference of the supported values.
|
||||
|
||||
For example, to specify the amount of workers you can use `NUnit.NumberOfTestWorkers` or to enable `DEBUG` logs `RunConfiguration.EnvironmentVariables`.
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RunSettings>
|
||||
<!-- NUnit adapter -->
|
||||
<NUnit>
|
||||
<NumberOfTestWorkers>24</NumberOfTestWorkers>
|
||||
</NUnit>
|
||||
<!-- General run configuration -->
|
||||
<RunConfiguration>
|
||||
<EnvironmentVariables>
|
||||
<!-- For debugging selectors, it's recommend to set the following environment variable -->
|
||||
<DEBUG>pw:api</DEBUG>
|
||||
</EnvironmentVariables>
|
||||
</RunConfiguration>
|
||||
<!-- Playwright -->
|
||||
<Playwright>
|
||||
<BrowserName>chromium</BrowserName>
|
||||
<ExpectTimeout>5000</ExpectTimeout>
|
||||
<LaunchOptions>
|
||||
<Headless>false</Headless>
|
||||
<Channel>msedge</Channel>
|
||||
</LaunchOptions>
|
||||
</Playwright>
|
||||
</RunSettings>
|
||||
```
|
||||
|
||||
### Base NUnit classes for Playwright
|
||||
|
||||
There are a few base classes available to you in `Microsoft.Playwright.NUnit` namespace:
|
||||
|
||||
|Test |Description|
|
||||
|--------------|-----------|
|
||||
|PageTest |Each test gets a fresh copy of a web [Page] created in its own unique [BrowserContext]. Extending this class is the simplest way of writing a fully-functional Playwright test.<br></br><br></br>Note: You can override the `ContextOptions` method in each test file to control context options, the ones typically passed into the [`method: Browser.newContext`] method. That way you can specify all kinds of emulation options for your test file individually.|
|
||||
|ContextTest |Each test will get a fresh copy of a [BrowserContext]. You can create as many pages in this context as you'd like. Using this test is the easiest way to test multi-page scenarios where you need more than one tab.<br></br><br></br>Note: You can override the `ContextOptions` method in each test file to control context options, the ones typically passed into the [`method: Browser.newContext`] method. That way you can specify all kinds of emulation options for your test file individually.|
|
||||
|BrowserTest |Each test will get a browser and can create as many contexts as it likes. Each test is responsible for cleaning up all the contexts it created.|
|
||||
|PlaywrightTest|This gives each test a Playwright object so that the test could start and stop as many browsers as it likes.|
|
||||
|
||||
## xUnit support
|
||||
|
||||
While using xUnit is also supported, we do not support running parallel tests. This is a well known problem/design limitation
|
||||
|
|
|
@ -18,10 +18,10 @@ Traces can be recorded using the [`property: BrowserContext.tracing`] API as fol
|
|||
|
||||
<Tabs
|
||||
groupId="test-runners"
|
||||
defaultValue="nunit"
|
||||
defaultValue="mstest"
|
||||
values={[
|
||||
{label: 'MSTest', value: 'mstest'},
|
||||
{label: 'NUnit', value: 'nunit'},
|
||||
{label: 'MSTest', value: 'mstest'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="nunit">
|
||||
|
@ -134,4 +134,4 @@ Check out our detailed guide on [Trace Viewer](/trace-viewer.md) to learn more a
|
|||
## What's next
|
||||
|
||||
- [Run tests on CI with GitHub Actions](/ci-intro.md)
|
||||
- [Learn more about the NUnit and MSTest base classes](./test-runners.md)
|
||||
- [Learn more about the MSTest and NUnit base classes](./test-runners.md)
|
||||
|
|
|
@ -244,10 +244,10 @@ Traces can be recorded using the [`property: BrowserContext.tracing`] API as fol
|
|||
|
||||
<Tabs
|
||||
groupId="test-runners"
|
||||
defaultValue="nunit"
|
||||
defaultValue="mstest"
|
||||
values={[
|
||||
{label: 'MSTest', value: 'mstest'},
|
||||
{label: 'NUnit', value: 'nunit'},
|
||||
{label: 'MSTest', value: 'mstest'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="nunit">
|
||||
|
@ -355,10 +355,10 @@ Setup your tests to record a trace only when the test fails:
|
|||
|
||||
<Tabs
|
||||
groupId="test-runners"
|
||||
defaultValue="nunit"
|
||||
defaultValue="mstest"
|
||||
values={[
|
||||
{label: 'MSTest', value: 'mstest'},
|
||||
{label: 'NUnit', value: 'nunit'},
|
||||
{label: 'MSTest', value: 'mstest'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="nunit">
|
||||
|
|
|
@ -347,14 +347,14 @@ def test_webview2(page: Page):
|
|||
|
||||
```csharp
|
||||
// WebView2Test.cs
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Playwright.NUnit;
|
||||
using Microsoft.Playwright;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Playwright;
|
||||
using Microsoft.Playwright.MSTest;
|
||||
|
||||
namespace dotnet_nunit;
|
||||
namespace PlaywrightTests;
|
||||
|
||||
public class WebView2Test : PlaywrightTest
|
||||
[TestClass]
|
||||
public class ExampleTest : PlaywrightTest
|
||||
{
|
||||
public IBrowser Browser { get; internal set; } = null!;
|
||||
public IBrowserContext Context { get; internal set; } = null!;
|
||||
|
@ -363,12 +363,12 @@ public class WebView2Test : PlaywrightTest
|
|||
private string _userDataDir = null!;
|
||||
private string _executablePath = Path.Join(Directory.GetCurrentDirectory(), @"..\..\..\..\webview2-app\bin\Debug\net8.0-windows\webview2.exe");
|
||||
|
||||
[SetUp]
|
||||
public async Task BrowserSetUp()
|
||||
[TestInitialize]
|
||||
public async Task BrowserTestInitialize()
|
||||
{
|
||||
var cdpPort = 10000 + WorkerIndex;
|
||||
Assert.IsTrue(File.Exists(_executablePath), "Make sure that the executable exists");
|
||||
_userDataDir = Path.Join(Path.GetTempPath(), $"playwright-webview2-tests/user-data-dir-{TestContext.CurrentContext.WorkerId}");
|
||||
_userDataDir = Path.Join(Path.GetTempPath(), $"playwright-webview2-tests/user-data-dir-{WorkerIndex}");
|
||||
// WebView2 does some lazy cleanups on shutdown so we can't clean it up after each test
|
||||
if (Directory.Exists(_userDataDir))
|
||||
{
|
||||
|
@ -401,8 +401,8 @@ public class WebView2Test : PlaywrightTest
|
|||
Page = Context.Pages[0];
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public async Task BrowserTearDown()
|
||||
[TestCleanup]
|
||||
public async Task BrowserTestCleanup()
|
||||
{
|
||||
_webView2Process!.Kill(true);
|
||||
await Browser.CloseAsync();
|
||||
|
@ -412,14 +412,15 @@ public class WebView2Test : PlaywrightTest
|
|||
|
||||
```csharp
|
||||
// UnitTest1.cs
|
||||
using Microsoft.Playwright.NUnit;
|
||||
using Microsoft.Playwright;
|
||||
using Microsoft.Playwright.MSTest;
|
||||
|
||||
namespace dotnet_nunit;
|
||||
namespace PlaywrightTests;
|
||||
|
||||
[Parallelizable(ParallelScope.Self)]
|
||||
public class Tests : WebView2Test
|
||||
[TestClass]
|
||||
public class ExampleTest : WebView2Test
|
||||
{
|
||||
[Test]
|
||||
[TestMethod]
|
||||
public async Task HomepageHasPlaywrightInTitleAndGetStartedLinkLinkingtoTheIntroPage()
|
||||
{
|
||||
await Page.GotoAsync("https://playwright.dev");
|
||||
|
|
|
@ -35,10 +35,10 @@ Take a look at the following example to see how to write a test.
|
|||
|
||||
<Tabs
|
||||
groupId="test-runners"
|
||||
defaultValue="nunit"
|
||||
defaultValue="mstest"
|
||||
values={[
|
||||
{label: 'MSTest', value: 'mstest'},
|
||||
{label: 'NUnit', value: 'nunit'},
|
||||
{label: 'MSTest', value: 'mstest'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="nunit">
|
||||
|
@ -200,10 +200,10 @@ The Playwright NUnit and MSTest test framework base classes will isolate each te
|
|||
|
||||
<Tabs
|
||||
groupId="test-runners"
|
||||
defaultValue="nunit"
|
||||
defaultValue="mstest"
|
||||
values={[
|
||||
{label: 'MSTest', value: 'mstest'},
|
||||
{label: 'NUnit', value: 'nunit'},
|
||||
{label: 'MSTest', value: 'mstest'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="nunit">
|
||||
|
@ -257,10 +257,10 @@ You can use `SetUp`/`TearDown` in NUnit or `TestInitialize`/`TestCleanup` in MST
|
|||
|
||||
<Tabs
|
||||
groupId="test-runners"
|
||||
defaultValue="nunit"
|
||||
defaultValue="mstest"
|
||||
values={[
|
||||
{label: 'MSTest', value: 'mstest'},
|
||||
{label: 'NUnit', value: 'nunit'},
|
||||
{label: 'MSTest', value: 'mstest'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="nunit">
|
||||
|
@ -328,4 +328,4 @@ public class ExampleTest : PageTest
|
|||
- [Generate tests with Codegen](./codegen-intro.md)
|
||||
- [See a trace of your tests](./trace-viewer-intro.md)
|
||||
- [Run tests on CI](./ci-intro.md)
|
||||
- [Learn more about the NUnit and MSTest base classes](./test-runners.md)
|
||||
- [Learn more about the MSTest and NUnit base classes](./test-runners.md)
|
||||
|
|
Загрузка…
Ссылка в новой задаче