diff --git a/dotnet/docs/api-testing.mdx b/dotnet/docs/api-testing.mdx index cab85d9d6a..7555532403 100644 --- a/dotnet/docs/api-testing.mdx +++ b/dotnet/docs/api-testing.mdx @@ -17,7 +17,7 @@ Sometimes you may want to send requests to the server directly from .NET without All of that could be achieved via [APIRequestContext] methods. -The following examples rely on the [`Microsoft.Playwright.NUnit`](./test-runners.mdx) package which creates a Playwright and Page instance for each test. +The following examples rely on the [`Microsoft.Playwright.MSTest`](./test-runners.mdx) package which creates a Playwright and Page instance for each test. @@ -35,22 +35,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(); @@ -72,7 +69,7 @@ public class TestGitHubAPI : PlaywrightTest }); } - [TearDown] + [TestCleanup] public async Task TearDownAPITesting() { await Request.DisposeAsync(); @@ -85,36 +82,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(); - data.Add("title", "[Bug] report 1"); - data.Add("body", "Bug description"); + var data = new Dictionary + { + { "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()) @@ -127,23 +122,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(); - data.Add("title", "[Feature] request 1"); - data.Add("body", "Feature description"); + var data = new Dictionary + { + { "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()) @@ -156,7 +152,7 @@ public class TestGitHubAPI : PlaywrightTest } } } - Assert.NotNull(issue); + Assert.IsNotNull(issue); Assert.AreEqual("Feature description", issue?.GetProperty("body").GetString()); } @@ -169,41 +165,47 @@ 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] - public async Task SetUpAPITesting() - { - await CreateAPIRequestContext(); - await CreateTestRepository(); - } - - private async Task CreateTestRepository() - { - var resp = await Request.PostAsync("/user/repos", new() - { - DataObject = new Dictionary() - { - ["name"] = REPO, - }, - }); - Assert.True(resp.Ok); - } - - [TearDown] - public async Task TearDownAPITesting() - { - await DeleteTestRepository(); - await Request.DisposeAsync(); - } - - private async Task DeleteTestRepository() - { - var resp = await Request.DeleteAsync("/repos/" + USER + "/" + REPO); - Assert.True(resp.Ok); - } + // ... + [TestInitialize] + public async Task SetUpAPITesting() + { + await CreateAPIRequestContext(); + await CreateTestRepository(); + } + + private async Task CreateTestRepository() + { + var resp = await Request.PostAsync("/user/repos", new() + { + DataObject = new Dictionary() + { + ["name"] = REPO, + }, + }); + await Expect(resp).ToBeOKAsync(); + } + + [TestCleanup] + public async Task TearDownAPITesting() + { + await DeleteTestRepository(); + await Request.DisposeAsync(); + } + + private async Task DeleteTestRepository() + { + var resp = await Request.DeleteAsync("/repos/" + USER + "/" + REPO); + await Expect(resp).ToBeOKAsync(); + } } ``` @@ -212,36 +214,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(); - data.Add("title", "[Bug] report 1"); - data.Add("body", "Bug description"); + var data = new Dictionary + { + { "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()) @@ -254,23 +254,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(); - data.Add("title", "[Feature] request 1"); - data.Add("body", "Feature description"); + var data = new Dictionary + { + { "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()) @@ -283,11 +284,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(); @@ -296,14 +297,16 @@ public class TestGitHubAPI : PlaywrightTest private async Task CreateAPIRequestContext() { - var headers = new Dictionary(); - // We set this header per GitHub guidelines. - headers.Add("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); - - Request = await this.Playwright.APIRequest.NewContextAsync(new() + var headers = new Dictionary + { + // We set this header per GitHub guidelines. + { "Accept", "application/vnd.github.v3+json" }, + // Add authorization token to all requests. + // Assuming personal access token available in the environment. + { "Authorization", "token " + API_TOKEN } + }; + + Request = await Playwright.APIRequest.NewContextAsync(new() { // All requests we send go to this API endpoint. BaseURL = "https://api.github.com", @@ -320,10 +323,10 @@ public class TestGitHubAPI : PlaywrightTest ["name"] = REPO, }, }); - Assert.True(resp.Ok); + await Expect(resp).ToBeOKAsync(); } - [TearDown] + [TestCleanup] public async Task TearDownAPITesting() { await DeleteTestRepository(); @@ -333,7 +336,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(); } } ``` @@ -345,21 +348,23 @@ The following test creates a new issue via API and then navigates to the list of ```csharp class TestGitHubAPI : PageTest { - [Test] - public async Task LastCreatedIssueShouldBeFirstInTheList() - { - var data = new Dictionary(); - data.Add("title", "[Feature] request 1"); - data.Add("body", "Feature description"); - var newIssue = await Request.PostAsync("/repos/" + USER + "/" + REPO + "/issues", new() { DataObject = data }); - Assert.True(newIssue.Ok); - - // 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. - await Page.GotoAsync("https://github.com/" + USER + "/" + REPO + "/issues"); - var firstIssue = Page.Locator("a[data-hovercard-type='issue']").First; - await Expect(firstIssue).ToHaveTextAsync("[Feature] request 1"); - } + [TestMethod] + public async Task LastCreatedIssueShouldBeFirstInTheList() + { + var data = new Dictionary + { + { "title", "[Feature] request 1" }, + { "body", "Feature description" } + }; + var newIssue = await Request.PostAsync("/repos/" + USER + "/" + REPO + "/issues", new() { DataObject = data }); + 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. + await Page.GotoAsync("https://github.com/" + USER + "/" + REPO + "/issues"); + var firstIssue = Page.Locator("a[data-hovercard-type='issue']").First; + await Expect(firstIssue).ToHaveTextAsync("[Feature] request 1"); + } } ``` @@ -368,22 +373,23 @@ class TestGitHubAPI : PageTest The following test creates a new issue via user interface in the browser and then checks via API if it was created: ```csharp +// Make sure to extend from PageTest if you want to use the Page class. class GitHubTests : PageTest { - [Test] - public async Task LastCreatedIssueShouldBeOnTheServer() - { - await Page.GotoAsync("https://github.com/" + USER + "/" + REPO + "/issues"); - await Page.Locator("text=New Issue").ClickAsync(); - 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 newIssue = await Request.GetAsync("https://github.com/" + USER + "/" + REPO + "/issues/" + issueId); - Assert.True(newIssue.Ok); - StringAssert.Contains(await newIssue.TextAsync(), "Bug report 1"); - } + [TestMethod] + public async Task LastCreatedIssueShouldBeOnTheServer() + { + await Page.GotoAsync("https://github.com/" + USER + "/" + REPO + "/issues"); + await Page.Locator("text=New Issue").ClickAsync(); + 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(); + var issueId = Page.Url.Substring(Page.Url.LastIndexOf('/')); + + var newIssue = await Request.GetAsync("https://github.com/" + USER + "/" + REPO + "/issues/" + issueId); + await Expect(newIssue).ToBeOKAsync(); + StringAssert.Contains(await newIssue.TextAsync(), "Bug report 1"); + } } ``` diff --git a/dotnet/docs/api/class-browsercontext.mdx b/dotnet/docs/api/class-browsercontext.mdx index 7f23acc555..72e7f0e11f 100644 --- a/dotnet/docs/api/class-browsercontext.mdx +++ b/dotnet/docs/api/class-browsercontext.mdx @@ -434,21 +434,22 @@ await BrowserContext.GrantPermissionsAsync(permissions, options); - `permissions` [IEnumerable]<[string]># A permission or an array of permissions to grant. Permissions can be one of the following values: - * `'geolocation'` - * `'midi'` - * `'midi-sysex'` (system-exclusive midi) - * `'notifications'` - * `'camera'` - * `'microphone'` - * `'background-sync'` - * `'ambient-light-sensor'` * `'accelerometer'` - * `'gyroscope'` - * `'magnetometer'` * `'accessibility-events'` + * `'ambient-light-sensor'` + * `'background-sync'` + * `'camera'` * `'clipboard-read'` * `'clipboard-write'` + * `'geolocation'` + * `'gyroscope'` + * `'magnetometer'` + * `'microphone'` + * `'midi-sysex'` (system-exclusive midi) + * `'midi'` + * `'notifications'` * `'payment-handler'` + * `'storage-access'` - `options` `BrowserContextGrantPermissionsOptions?` *(optional)* - `Origin` [string]? *(optional)*# diff --git a/dotnet/docs/api/class-clock.mdx b/dotnet/docs/api/class-clock.mdx index b706c0ff63..3776885dc5 100644 --- a/dotnet/docs/api/class-clock.mdx +++ b/dotnet/docs/api/class-clock.mdx @@ -16,165 +16,170 @@ Note that clock is installed for the entire [BrowserContext], so the time in all ## Methods -### InstallFakeTimersAsync {#clock-install-fake-timers} +### FastForwardAsync {#clock-fast-forward} -Added in: v1.45clock.InstallFakeTimersAsync +Added in: v1.45clock.FastForwardAsync -Install fake implementations for the following time-related functions: -* `setTimeout` -* `clearTimeout` -* `setInterval` -* `clearInterval` -* `requestAnimationFrame` -* `cancelAnimationFrame` -* `requestIdleCallback` -* `cancelIdleCallback` -* `performance` - -Fake timers are used to manually control the flow of time in tests. They allow you to advance time, fire timers, and control the behavior of time-dependent functions. See [Clock.RunForAsync()](/api/class-clock.mdx#clock-run-for) and [Clock.SkipTimeAsync()](/api/class-clock.mdx#clock-skip-time) for more information. +Advance the clock by jumping forward in time. Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and reopening it later, after given time. **Usage** ```csharp -await Clock.InstallFakeTimersAsync(time, options); +await page.Clock.FastForwardAsync(1000); +await page.Clock.FastForwardAsync("30:00"); ``` **Arguments** -- `time` [int] | [Date]# +- `ticks` [int] | [string]# - Install fake timers with the specified base time. -- `options` `ClockInstallFakeTimersOptions?` *(optional)* - - `LoopLimit` [int]? *(optional)*# - - The maximum number of timers that will be run in [Clock.RunAllTimersAsync()](/api/class-clock.mdx#clock-run-all-timers). Defaults to `1000`. + Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds. **Returns** -- [void]# +- [void]# --- -### RunAllTimersAsync {#clock-run-all-timers} +### InstallAsync {#clock-install} -Added in: v1.45clock.RunAllTimersAsync +Added in: v1.45clock.InstallAsync -Runs all pending timers until there are none remaining. If new timers are added while it is executing they will be run as well. Fake timers must be installed. Returns fake milliseconds since the unix epoch. +Install fake implementations for the following time-related functions: +* `Date` +* `setTimeout` +* `clearTimeout` +* `setInterval` +* `clearInterval` +* `requestAnimationFrame` +* `cancelAnimationFrame` +* `requestIdleCallback` +* `cancelIdleCallback` +* `performance` + +Fake timers are used to manually control the flow of time in tests. They allow you to advance time, fire timers, and control the behavior of time-dependent functions. See [Clock.RunForAsync()](/api/class-clock.mdx#clock-run-for) and [Clock.FastForwardAsync()](/api/class-clock.mdx#clock-fast-forward) for more information. **Usage** ```csharp -await Clock.RunAllTimersAsync(); +await Clock.InstallAsync(options); ``` -**Returns** -- [int]# - -**Details** +**Arguments** +- `options` `ClockInstallOptions?` *(optional)* + - `TimeInt|Time|TimeDate` [int]? | [string]? | [Date]? *(optional)*# + + Time to initialize with, current system time by default. -This makes it easier to run asynchronous tests to completion without worrying about the number of timers they use, or the delays in those timers. It runs a maximum of `loopLimit` times after which it assumes there is an infinite loop of timers and throws an error. +**Returns** +- [void]# --- -### RunForAsync {#clock-run-for} +### PauseAtAsync {#clock-pause-at} -Added in: v1.45clock.RunForAsync +Added in: v1.45clock.PauseAtAsync -Advance the clock, firing callbacks if necessary. Returns fake milliseconds since the unix epoch. Fake timers must be installed. Returns fake milliseconds since the unix epoch. +Advance the clock by jumping forward in time and pause the time. Once this method is called, no timers are fired unless [Clock.RunForAsync()](/api/class-clock.mdx#clock-run-for), [Clock.FastForwardAsync()](/api/class-clock.mdx#clock-fast-forward), [Clock.PauseAtAsync()](/api/class-clock.mdx#clock-pause-at) or [Clock.ResumeAsync()](/api/class-clock.mdx#clock-resume) is called. + +Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and reopening it at the specified time and pausing. **Usage** ```csharp -await page.Clock.RunForAsync(1000); -await page.Clock.RunForAsync("30:00"); +await page.Clock.PauseAtAsync(DateTime.Parse("2020-02-02")); +await page.Clock.PauseAtAsync("2020-02-02"); ``` **Arguments** -- `time` [int] | [string]# - - Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds. +- `time` [int] | [string] | [Date]# **Returns** -- [int]# +- [void]# --- -### RunToLastTimerAsync {#clock-run-to-last-timer} +### ResumeAsync {#clock-resume} -Added in: v1.45clock.RunToLastTimerAsync +Added in: v1.45clock.ResumeAsync -This takes note of the last scheduled timer when it is run, and advances the clock to that time firing callbacks as necessary. If new timers are added while it is executing they will be run only if they would occur before this time. This is useful when you want to run a test to completion, but the test recursively sets timers that would cause runAll to trigger an infinite loop warning. Fake timers must be installed. Returns fake milliseconds since the unix epoch. +Resumes timers. Once this method is called, time resumes flowing, timers are fired as usual. **Usage** ```csharp -await Clock.RunToLastTimerAsync(); +await Clock.ResumeAsync(); ``` **Returns** -- [int]# +- [void]# --- -### RunToNextTimerAsync {#clock-run-to-next-timer} +### RunForAsync {#clock-run-for} -Added in: v1.45clock.RunToNextTimerAsync +Added in: v1.45clock.RunForAsync -Advances the clock to the moment of the first scheduled timer, firing it. Fake timers must be installed. Returns fake milliseconds since the unix epoch. +Advance the clock, firing all the time-related callbacks. **Usage** ```csharp -await Clock.RunToNextTimerAsync(); +await page.Clock.RunForAsync(1000); +await page.Clock.RunForAsync("30:00"); ``` +**Arguments** +- `ticks` [int] | [string]# + + Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds. + **Returns** -- [int]# +- [void]# --- -### SetTimeAsync {#clock-set-time} - -Added in: v1.45clock.SetTimeAsync +### SetFixedTimeAsync {#clock-set-fixed-time} -Set the clock to the specified time. +Added in: v1.45clock.SetFixedTimeAsync -When fake timers are installed, only fires timers at most once. This can be used to simulate the JS engine (such as a browser) being put to sleep and resumed later, skipping intermediary timers. +Makes `Date.now` and `new Date()` return fixed fake time at all times, keeps all the timers running. **Usage** ```csharp -await Clock.SetTimeAsync(time); +await page.Clock.SetFixedTimeAsync(DateTime.Now); +await page.Clock.SetFixedTimeAsync(new DateTime(2020, 2, 2)); +await page.Clock.SetFixedTimeAsync("2020-02-02"); ``` **Arguments** -- `time` [int] | [Date]# +- `time` [int] | [string] | [Date]# + + Time to be set. **Returns** -- [void]# +- [void]# --- -### SkipTimeAsync {#clock-skip-time} +### SetSystemTimeAsync {#clock-set-system-time} -Added in: v1.45clock.SkipTimeAsync +Added in: v1.45clock.SetSystemTimeAsync -Advance the clock by jumping forward in time, equivalent to running [Clock.SetTimeAsync()](/api/class-clock.mdx#clock-set-time) with the new target time. - -When fake timers are installed, [Clock.SkipTimeAsync()](/api/class-clock.mdx#clock-skip-time) only fires due timers at most once, while [Clock.RunForAsync()](/api/class-clock.mdx#clock-run-for) fires all the timers up to the current time. Returns fake milliseconds since the unix epoch. +Sets current system time but does not trigger any timers. **Usage** ```csharp -await page.Clock.SkipTimeAsync(1000); -await page.Clock.SkipTimeAsync("30:00"); +await page.Clock.SetSystemTimeAsync(DateTime.Now); +await page.Clock.SetSystemTimeAsync(new DateTime(2020, 2, 2)); +await page.Clock.SetSystemTimeAsync("2020-02-02"); ``` **Arguments** -- `time` [int] | [string]# - - Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds. +- `time` [int] | [string] | [Date]# **Returns** -- [int]# +- [void]# [Accessibility]: /api/class-accessibility.mdx "Accessibility" diff --git a/dotnet/docs/api/class-locatorassertions.mdx b/dotnet/docs/api/class-locatorassertions.mdx index ff7389e2d4..1d0f8f9b27 100644 --- a/dotnet/docs/api/class-locatorassertions.mdx +++ b/dotnet/docs/api/class-locatorassertions.mdx @@ -10,21 +10,19 @@ import HTMLCard from '@site/src/components/HTMLCard'; The [LocatorAssertions] class provides assertion methods that can be used to make assertions about the [Locator] state in the tests. ```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"); } } diff --git a/dotnet/docs/api/class-pageassertions.mdx b/dotnet/docs/api/class-pageassertions.mdx index 7ba95465df..196848eade 100644 --- a/dotnet/docs/api/class-pageassertions.mdx +++ b/dotnet/docs/api/class-pageassertions.mdx @@ -11,21 +11,19 @@ The [PageAssertions] class provides assertion methods that can be used to make a ```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")); } } ``` diff --git a/dotnet/docs/api/class-playwrightassertions.mdx b/dotnet/docs/api/class-playwrightassertions.mdx index 775b59b830..cf346f13e4 100644 --- a/dotnet/docs/api/class-playwrightassertions.mdx +++ b/dotnet/docs/api/class-playwrightassertions.mdx @@ -12,19 +12,18 @@ Playwright gives you Web-First Assertions with convenience methods for creating Consider the following example: ```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"); } } diff --git a/dotnet/docs/clock.mdx b/dotnet/docs/clock.mdx index 3d68949a70..0df673ae8e 100644 --- a/dotnet/docs/clock.mdx +++ b/dotnet/docs/clock.mdx @@ -20,66 +20,81 @@ Accurately simulating time-dependent behavior is essential for verifying the cor - `cancelAnimationFrame` - `requestIdleCallback` - `cancelIdleCallback` +- `performance` -By default, the clock starts at the unix epoch (timestamp of 0). You can override it using the `now` option. +## Test with predefined time -## Mock Date.now - -Most of the time, you only need to fake `Date.now` and no other time-related functions. That way the time flows naturally, but `Date.now` returns a fixed value. +Often you only need to fake `Date.now` while keeping the timers going. That way the time flows naturally, but `Date.now` always returns a fixed value. ```html
``` -```csharp -// Initialize clock with a specific time, only fake Date.now. -await page.Clock.SetTimeAsync(new DateTime(2024, 2, 2, 10, 0, 0, DateTimeKind.Pst)); -await page.GotoAsync("http://localhost:3333"); -var locator = page.GetByTestId("current-time"); -await Expect(locator).ToHaveTextAsync("2/2/2024, 10:00:00 AM"); - -await page.Clock.SetTimeAsync(new DateTime(2024, 2, 2, 10, 30, 0, DateTimeKind.Pst)); -await Expect(locator).ToHaveTextAsync("2/2/2024, 10:30:00 AM"); -``` - -## Mock Date.now consistent with the timers +## Consistent time and timers -Sometimes your timers depend on `Date.now` and are confused when the time stands still. In cases like this you need to ensure that `Date.now` and timers are consistent. You can achieve this by installing the fake timers. +Sometimes your timers depend on `Date.now` and are confused when the `Date.now` value does not change over time. In this case, you can install the clock and fast forward to the time of interest when testing. ```html
``` ```csharp -// Initialize clock with a specific time, take full control over time. -await page.Clock.InstallFakeTimersAsync( - new DateTime(2024, 2, 2, 10, 0, 0, DateTimeKind.Pst) -); +// Initialize clock with some time before the test time and let the page load naturally. +// `Date.now` will progress as the timers fire. +await Page.Clock.InstallAsync(new +{ + Time = new DateTime(2024, 2, 2, 8, 0, 0) +}); +await Page.GotoAsync("http://localhost:3333"); + +// Pretend that the user closed the laptop lid and opened it again at 10am. +// Pause the time once reached that point. +await Page.Clock.PauseAtAsync(new DateTime(2024, 2, 2, 10, 0, 0)); + +// Assert the page state. +await Expect(Page.GetByTestId("current-time")).ToHaveText("2/2/2024, 10:00:00 AM"); + +// Close the laptop lid again and open it at 10:30am. +await Page.Clock.FastForwardAsync("30:00"); +await Expect(Page.GetByTestId("current-time")).ToHaveText("2/2/2024, 10:30:00 AM"); +``` + +## Test inactivity monitoring + +Inactivity monitoring is a common feature in web applications that logs out users after a period of inactivity. Testing this feature can be tricky because you need to wait for a long time to see the effect. With the help of the clock, you can speed up time and test this feature quickly. + +```csharp +// Initial time does not matter for the test, so we can pick current time. +await Page.Clock.InstallAsync(); await page.GotoAsync("http://localhost:3333"); -var locator = page.GetByTestId("current-time"); -await Expect(locator).ToHaveTextAsync("2/2/2024, 10:00:00 AM"); -// Fast forward time 30 minutes without firing intermediate timers, as if the user -// closed and opened the lid of the laptop. -await page.Clock.SkipTimeAsync("30:00"); -await Expect(locator).ToHaveTextAsync("2/2/2024, 10:30:00 AM"); +// Interact with the page +await page.GetByRole("button").ClickAsync(); + +// Fast forward time 5 minutes as if the user did not do anything. +// Fast forward is like closing the laptop lid and opening it after 5 minutes. +// All the timers due will fire once immediately, as in the real browser. +await Page.Clock.FastForwardAsync("5:00"); + +// Check that the user was logged out automatically. +await Expect(Page.GetByText("You have been logged out due to inactivity.")).ToBeVisibleAsync(); ``` -## Tick through time manually +## Tick through time manually, firing all the timers consistently In rare cases, you may want to tick through time manually, firing all timers and animation frames in the process to achieve a fine-grained control over the passage of time. @@ -88,23 +103,29 @@ In rare cases, you may want to tick through time manually, firing all timers and ``` ```csharp -// Initialize clock with a specific time, take full control over time. -await page.Clock.InstallFakeTimersAsync( - new DateTime(2024, 2, 2, 10, 0, 0, DateTimeKind.Pst) -); +// Initialize clock with a specific time, let the page load naturally. +await Page.Clock.InstallAsync(new +{ + Time = new DateTime(2024, 2, 2, 8, 0, 0, DateTimeKind.Pst) +}); await page.GotoAsync("http://localhost:3333"); var locator = page.GetByTestId("current-time"); +// Pause the time flow, stop the timers, you now have manual control +// over the page time. +await Page.Clock.PauseAtAsync(new DateTime(2024, 2, 2, 10, 0, 0)); +await Expect(locator).ToHaveTextAsync("2/2/2024, 10:00:00 AM"); + // Tick through time manually, firing all timers in the process. // In this case, time will be updated in the screen 2 times. -await page.Clock.RunForAsync(2000); +await Page.Clock.RunForAsync(2000); await Expect(locator).ToHaveTextAsync("2/2/2024, 10:00:02 AM"); ``` diff --git a/dotnet/docs/intro.mdx b/dotnet/docs/intro.mdx index f47779b9c7..e0b11d235b 100644 --- a/dotnet/docs/intro.mdx +++ b/dotnet/docs/intro.mdx @@ -10,10 +10,10 @@ import HTMLCard from '@site/src/components/HTMLCard'; 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.mdx#nunit) or [MSTest base classes](./test-runners.mdx#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.mdx) to manually write the testing infrastructure. +You can choose to use [MSTest base classes](./test-runners.mdx#mstest) or [NUnit base classes](./test-runners.mdx#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.mdx) 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: - + @@ -37,7 +37,7 @@ cd PlaywrightTests 2. Install the necessary Playwright dependencies: - + @@ -75,7 +75,7 @@ If `pwsh` is not available, you will have to [install PowerShell](https://docs.m Edit the `UnitTest1.cs` file with the code below to create an example end-to-end test: - + @@ -121,10 +121,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; @@ -180,7 +178,7 @@ See our doc on [Running and Debugging Tests](./running-tests.mdx) to learn more - [Generate tests with Codegen](./codegen-intro.mdx) - [See a trace of your tests](./trace-viewer-intro.mdx) - [Run tests on CI](./ci-intro.mdx) -- [Learn more about the NUnit and MSTest base classes](./test-runners.mdx) +- [Learn more about the MSTest and NUnit base classes](./test-runners.mdx) [Accessibility]: /api/class-accessibility.mdx "Accessibility" diff --git a/dotnet/docs/languages.mdx b/dotnet/docs/languages.mdx index 7d2d9be734..28d8456f8c 100644 --- a/dotnet/docs/languages.mdx +++ b/dotnet/docs/languages.mdx @@ -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) diff --git a/dotnet/docs/library.mdx b/dotnet/docs/library.mdx index 0952185ae2..3ac55d5b03 100644 --- a/dotnet/docs/library.mdx +++ b/dotnet/docs/library.mdx @@ -8,7 +8,7 @@ import HTMLCard from '@site/src/components/HTMLCard'; ## Introduction -Playwright can either be used with the [NUnit](./test-runners.mdx#nunit) or [MSTest](./test-runners.mdx#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.mdx#mstest) or [NUnit](./test-runners.mdx#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 diff --git a/dotnet/docs/release-notes.mdx b/dotnet/docs/release-notes.mdx index 99ac3a794f..23074f1c93 100644 --- a/dotnet/docs/release-notes.mdx +++ b/dotnet/docs/release-notes.mdx @@ -562,7 +562,7 @@ This version was also tested against the following stable channels: ### Other highlights - New option `MaxRedirects` for [ApiRequestContext.GetAsync()](/api/class-apirequestcontext.mdx#api-request-context-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 diff --git a/dotnet/docs/running-tests.mdx b/dotnet/docs/running-tests.mdx index bd57295f21..98daa90481 100644 --- a/dotnet/docs/running-tests.mdx +++ b/dotnet/docs/running-tests.mdx @@ -149,7 +149,7 @@ dotnet test --filter "Name~GetStartedLink" ### Run tests with multiple workers: - + @@ -215,7 +215,7 @@ Check out our [debugging guide](./debug.mdx) to learn more about the [Playwright - [Generate tests with Codegen](./codegen-intro.mdx) - [See a trace of your tests](./trace-viewer-intro.mdx) - [Run tests on CI](./ci-intro.mdx) -- [Learn more about the NUnit and MSTest base classes](./test-runners.mdx) +- [Learn more about the MSTest and NUnit base classes](./test-runners.mdx) [Accessibility]: /api/class-accessibility.mdx "Accessibility" diff --git a/dotnet/docs/test-assertions.mdx b/dotnet/docs/test-assertions.mdx index 69a2ca2c25..ab6c5c87b1 100644 --- a/dotnet/docs/test-assertions.mdx +++ b/dotnet/docs/test-assertions.mdx @@ -43,7 +43,7 @@ You can specify a custom timeout for assertions either globally or per assertion ### Global timeout - + diff --git a/dotnet/docs/test-runners.mdx b/dotnet/docs/test-runners.mdx index 90e457040d..9e12db2ad2 100644 --- a/dotnet/docs/test-runners.mdx +++ b/dotnet/docs/test-runners.mdx @@ -8,24 +8,22 @@ import HTMLCard from '@site/src/components/HTMLCard'; ## 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 recommend running each test case in a new BrowserContext, this way browser state will be isolated between the tests. +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 +## MSTest -Playwright provides base classes to write tests with NUnit via the [`Microsoft.Playwright.NUnit`](https://www.nuget.org/packages/Microsoft.Playwright.NUnit) package. +Playwright provides base classes to write tests with MSTest via the [`Microsoft.Playwright.MSTest`](https://www.nuget.org/packages/Microsoft.Playwright.MSTest) package. Check out the [installation guide](./intro.mdx) 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. +### Running MSTest tests in Parallel -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. +By default MSTest will run all classes in parallel, while running tests inside each class sequentially (`ExecutionScope.ClassLevel`). It will create as many processes as there are cores on the host system. You can adjust this behavior by using the following CLI parameter or using a `.runsettings` file, see below. Running tests in parallel at the method level (`ExecutionScope.MethodLevel`) is not supported. ```bash -dotnet test -- NUnit.NumberOfTestWorkers=5 +dotnet test --settings:.runsettings -- MSTest.Parallelize.Workers=4 ``` ### Customizing [BrowserContext] options @@ -33,15 +31,17 @@ dotnet test -- NUnit.NumberOfTestWorkers=5 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; +using System.Threading.Tasks; +using Microsoft.Playwright; +using Microsoft.Playwright.MSTest; +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace PlaywrightTests; -[Parallelizable(ParallelScope.Self)] -[TestFixture] -public class MyTest : PageTest +[TestClass] +public class ExampleTest : PageTest { - [Test] + [TestMethod] public async Task TestWithCustomContextOptions() { // The following Page (and BrowserContext) instance has the custom colorScheme, viewport and baseURL set: @@ -62,6 +62,7 @@ public class MyTest : PageTest }; } } + ``` ### Customizing [Browser]/launch options @@ -87,21 +88,23 @@ dotnet test -- Playwright.BrowserName=chromium Playwright.LaunchOptions.Headless ### Using Verbose API Logs -When you have enabled the [verbose API log](./debug.mdx#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. +When you have enabled the [verbose API log](./debug.mdx#verbose-api-logs), via the `DEBUG` environment variable, you will see the messages in the standard error stream. In MSTest, 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`. +For example, to specify the number of workers, you can use `MSTest.Parallelize.Workers`. You can also enable `DEBUG` logs using `RunConfiguration.EnvironmentVariables`. ```xml - - - - 24 - + + + + 4 + ClassLevel + + @@ -121,9 +124,9 @@ For example, to specify the amount of workers you can use `NUnit.NumberOfTestWor ``` -### Base NUnit classes for Playwright +### Base MSTest classes for Playwright -There are a few base classes available to you in `Microsoft.Playwright.NUnit` namespace: +There are a few base classes available to you in `Microsoft.Playwright.MSTest` namespace: |Test |Description| |--------------|-----------| @@ -132,18 +135,20 @@ There are a few base classes available to you in `Microsoft.Playwright.NUnit` na |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 +## NUnit -Playwright provides base classes to write tests with MSTest via the [`Microsoft.Playwright.MSTest`](https://www.nuget.org/packages/Microsoft.Playwright.MSTest) package. +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.mdx) to get started. -### Running MSTest tests in Parallel +### Running NUnit tests in Parallel -By default MSTest will run all classes in parallel, while running tests inside each class sequentially (`ExecutionScope.ClassLevel`). It will create as many processes as there are cores on the host system. You can adjust this behavior by using the following CLI parameter or using a `.runsettings` file, see below. Running tests in parallel at the method level (`ExecutionScope.MethodLevel`) is not supported. +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 --settings:.runsettings -- MSTest.Parallelize.Workers=4 +dotnet test -- NUnit.NumberOfTestWorkers=5 ``` ### Customizing [BrowserContext] options @@ -151,17 +156,15 @@ dotnet test --settings:.runsettings -- MSTest.Parallelize.Workers=4 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 System.Threading.Tasks; -using Microsoft.Playwright; -using Microsoft.Playwright.MSTest; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.Playwright.NUnit; namespace PlaywrightTests; -[TestClass] -public class ExampleTest : PageTest +[Parallelizable(ParallelScope.Self)] +[TestFixture] +public class MyTest : PageTest { - [TestMethod] + [Test] public async Task TestWithCustomContextOptions() { // The following Page (and BrowserContext) instance has the custom colorScheme, viewport and baseURL set: @@ -182,7 +185,6 @@ public class ExampleTest : PageTest }; } } - ``` ### Customizing [Browser]/launch options @@ -208,23 +210,21 @@ dotnet test -- Playwright.BrowserName=chromium Playwright.LaunchOptions.Headless ### Using Verbose API Logs -When you have enabled the [verbose API log](./debug.mdx#verbose-api-logs), via the `DEBUG` environment variable, you will see the messages in the standard error stream. In MSTest, 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. +When you have enabled the [verbose API log](./debug.mdx#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 number of workers, you can use `MSTest.Parallelize.Workers`. You can also enable `DEBUG` logs using `RunConfiguration.EnvironmentVariables`. +For example, to specify the amount of workers you can use `NUnit.NumberOfTestWorkers` or to enable `DEBUG` logs `RunConfiguration.EnvironmentVariables`. ```xml + - - - - 4 - ClassLevel - - + + + 24 + @@ -244,9 +244,9 @@ For example, to specify the number of workers, you can use `MSTest.Parallelize.W ``` -### Base MSTest classes for Playwright +### Base NUnit classes for Playwright -There are a few base classes available to you in `Microsoft.Playwright.MSTest` namespace: +There are a few base classes available to you in `Microsoft.Playwright.NUnit` namespace: |Test |Description| |--------------|-----------| diff --git a/dotnet/docs/trace-viewer-intro.mdx b/dotnet/docs/trace-viewer-intro.mdx index b94f7e070b..186839b1ee 100644 --- a/dotnet/docs/trace-viewer-intro.mdx +++ b/dotnet/docs/trace-viewer-intro.mdx @@ -18,7 +18,7 @@ Playwright Trace Viewer is a GUI tool that lets you explore recorded Playwright Traces can be recorded using the [BrowserContext.Tracing](/api/class-browsercontext.mdx#browser-context-tracing) API as follows: - + @@ -129,7 +129,7 @@ Check out our detailed guide on [Trace Viewer](/trace-viewer.mdx) to learn more ## What's next - [Run tests on CI with GitHub Actions](/ci-intro.mdx) -- [Learn more about the NUnit and MSTest base classes](./test-runners.mdx) +- [Learn more about the MSTest and NUnit base classes](./test-runners.mdx) [Accessibility]: /api/class-accessibility.mdx "Accessibility" diff --git a/dotnet/docs/trace-viewer.mdx b/dotnet/docs/trace-viewer.mdx index 0f7ae787c7..a3f17d57fd 100644 --- a/dotnet/docs/trace-viewer.mdx +++ b/dotnet/docs/trace-viewer.mdx @@ -97,7 +97,7 @@ Next to the Actions tab you will find the Metadata tab which will show you more Traces can be recorded using the [BrowserContext.Tracing](/api/class-browsercontext.mdx#browser-context-tracing) API as follows: - + @@ -202,7 +202,7 @@ This will record the trace and place it into the `bin/Debug/net8.0/playwright-tr Setup your tests to record a trace only when the test fails: - + diff --git a/dotnet/docs/webview2.mdx b/dotnet/docs/webview2.mdx index 4bcf6082ec..a1a2826e9c 100644 --- a/dotnet/docs/webview2.mdx +++ b/dotnet/docs/webview2.mdx @@ -51,14 +51,14 @@ Using the following, Playwright will run your WebView2 application as a sub-proc ```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!; @@ -67,12 +67,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)) { @@ -105,8 +105,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(); @@ -116,14 +116,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"); diff --git a/dotnet/docs/writing-tests.mdx b/dotnet/docs/writing-tests.mdx index ecb4a49371..0766cd708d 100644 --- a/dotnet/docs/writing-tests.mdx +++ b/dotnet/docs/writing-tests.mdx @@ -29,7 +29,7 @@ That's it! These design choices allow Playwright users to forget about flaky tim Take a look at the following example to see how to write a test. - + @@ -185,7 +185,7 @@ Here is the list of the most popular async assertions. Note that there are [many The Playwright NUnit and MSTest test framework base classes will isolate each test from each other by providing a separate `Page` instance. Pages are isolated between tests due to the Browser Context, which is equivalent to a brand new browser profile, where every test gets a fresh environment, even when multiple tests run in a single Browser. - + @@ -238,7 +238,7 @@ public class ExampleTest : PageTest You can use `SetUp`/`TearDown` in NUnit or `TestInitialize`/`TestCleanup` in MSTest to prepare and clean up your test environment: - + @@ -306,7 +306,7 @@ public class ExampleTest : PageTest - [Generate tests with Codegen](./codegen-intro.mdx) - [See a trace of your tests](./trace-viewer-intro.mdx) - [Run tests on CI](./ci-intro.mdx) -- [Learn more about the NUnit and MSTest base classes](./test-runners.mdx) +- [Learn more about the MSTest and NUnit base classes](./test-runners.mdx) [Accessibility]: /api/class-accessibility.mdx "Accessibility" diff --git a/java/docs/api/class-browsercontext.mdx b/java/docs/api/class-browsercontext.mdx index 3a35b2c47c..0d224434f7 100644 --- a/java/docs/api/class-browsercontext.mdx +++ b/java/docs/api/class-browsercontext.mdx @@ -437,21 +437,22 @@ BrowserContext.grantPermissions(permissions, options); - `permissions` [List]<[String]># A permission or an array of permissions to grant. Permissions can be one of the following values: - * `'geolocation'` - * `'midi'` - * `'midi-sysex'` (system-exclusive midi) - * `'notifications'` - * `'camera'` - * `'microphone'` - * `'background-sync'` - * `'ambient-light-sensor'` * `'accelerometer'` - * `'gyroscope'` - * `'magnetometer'` * `'accessibility-events'` + * `'ambient-light-sensor'` + * `'background-sync'` + * `'camera'` * `'clipboard-read'` * `'clipboard-write'` + * `'geolocation'` + * `'gyroscope'` + * `'magnetometer'` + * `'microphone'` + * `'midi-sysex'` (system-exclusive midi) + * `'midi'` + * `'notifications'` * `'payment-handler'` + * `'storage-access'` - `options` `BrowserContext.GrantPermissionsOptions` *(optional)* - `setOrigin` [String] *(optional)*# diff --git a/java/docs/api/class-clock.mdx b/java/docs/api/class-clock.mdx index f26c08c34a..0610058153 100644 --- a/java/docs/api/class-clock.mdx +++ b/java/docs/api/class-clock.mdx @@ -16,166 +16,171 @@ Note that clock is installed for the entire [BrowserContext], so the time in all ## Methods -### installFakeTimers {#clock-install-fake-timers} +### fastForward {#clock-fast-forward} -Added in: v1.45clock.installFakeTimers +Added in: v1.45clock.fastForward -Install fake implementations for the following time-related functions: -* `setTimeout` -* `clearTimeout` -* `setInterval` -* `clearInterval` -* `requestAnimationFrame` -* `cancelAnimationFrame` -* `requestIdleCallback` -* `cancelIdleCallback` -* `performance` - -Fake timers are used to manually control the flow of time in tests. They allow you to advance time, fire timers, and control the behavior of time-dependent functions. See [Clock.runFor()](/api/class-clock.mdx#clock-run-for) and [Clock.skipTime()](/api/class-clock.mdx#clock-skip-time) for more information. +Advance the clock by jumping forward in time. Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and reopening it later, after given time. **Usage** ```java -Clock.installFakeTimers(time); -Clock.installFakeTimers(time, options); +page.clock().fastForward(1000); +page.clock().fastForward("30:00"); ``` **Arguments** -- `time` [int] | [Date]# +- `ticks` [int] | [String]# - Install fake timers with the specified base time. -- `options` `Clock.InstallFakeTimersOptions` *(optional)* - - `setLoopLimit` [int] *(optional)*# - - The maximum number of timers that will be run in [Clock.runAllTimers()](/api/class-clock.mdx#clock-run-all-timers). Defaults to `1000`. + Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds. **Returns** -- [void]# +- [void]# --- -### runAllTimers {#clock-run-all-timers} +### install {#clock-install} -Added in: v1.45clock.runAllTimers +Added in: v1.45clock.install -Runs all pending timers until there are none remaining. If new timers are added while it is executing they will be run as well. Fake timers must be installed. Returns fake milliseconds since the unix epoch. +Install fake implementations for the following time-related functions: +* `Date` +* `setTimeout` +* `clearTimeout` +* `setInterval` +* `clearInterval` +* `requestAnimationFrame` +* `cancelAnimationFrame` +* `requestIdleCallback` +* `cancelIdleCallback` +* `performance` + +Fake timers are used to manually control the flow of time in tests. They allow you to advance time, fire timers, and control the behavior of time-dependent functions. See [Clock.runFor()](/api/class-clock.mdx#clock-run-for) and [Clock.fastForward()](/api/class-clock.mdx#clock-fast-forward) for more information. **Usage** ```java -Clock.runAllTimers(); +Clock.install(); +Clock.install(options); ``` -**Returns** -- [int]# - -**Details** +**Arguments** +- `options` `Clock.InstallOptions` *(optional)* + - `setTime` [int] | [String] | [Date] *(optional)*# + + Time to initialize with, current system time by default. -This makes it easier to run asynchronous tests to completion without worrying about the number of timers they use, or the delays in those timers. It runs a maximum of `loopLimit` times after which it assumes there is an infinite loop of timers and throws an error. +**Returns** +- [void]# --- -### runFor {#clock-run-for} +### pauseAt {#clock-pause-at} -Added in: v1.45clock.runFor +Added in: v1.45clock.pauseAt -Advance the clock, firing callbacks if necessary. Returns fake milliseconds since the unix epoch. Fake timers must be installed. Returns fake milliseconds since the unix epoch. +Advance the clock by jumping forward in time and pause the time. Once this method is called, no timers are fired unless [Clock.runFor()](/api/class-clock.mdx#clock-run-for), [Clock.fastForward()](/api/class-clock.mdx#clock-fast-forward), [Clock.pauseAt()](/api/class-clock.mdx#clock-pause-at) or [Clock.resume()](/api/class-clock.mdx#clock-resume) is called. + +Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and reopening it at the specified time and pausing. **Usage** ```java -page.clock().runFor(1000); -page.clock().runFor("30:00"); +page.clock().pauseAt(Instant.parse("2020-02-02")); +page.clock().pauseAt("2020-02-02"); ``` **Arguments** -- `time` [int] | [String]# - - Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds. +- `time` [int] | [String] | [Date]# **Returns** -- [int]# +- [void]# --- -### runToLastTimer {#clock-run-to-last-timer} +### resume {#clock-resume} -Added in: v1.45clock.runToLastTimer +Added in: v1.45clock.resume -This takes note of the last scheduled timer when it is run, and advances the clock to that time firing callbacks as necessary. If new timers are added while it is executing they will be run only if they would occur before this time. This is useful when you want to run a test to completion, but the test recursively sets timers that would cause runAll to trigger an infinite loop warning. Fake timers must be installed. Returns fake milliseconds since the unix epoch. +Resumes timers. Once this method is called, time resumes flowing, timers are fired as usual. **Usage** ```java -Clock.runToLastTimer(); +Clock.resume(); ``` **Returns** -- [int]# +- [void]# --- -### runToNextTimer {#clock-run-to-next-timer} +### runFor {#clock-run-for} -Added in: v1.45clock.runToNextTimer +Added in: v1.45clock.runFor -Advances the clock to the moment of the first scheduled timer, firing it. Fake timers must be installed. Returns fake milliseconds since the unix epoch. +Advance the clock, firing all the time-related callbacks. **Usage** ```java -Clock.runToNextTimer(); +page.clock().runFor(1000); +page.clock().runFor("30:00"); ``` +**Arguments** +- `ticks` [int] | [String]# + + Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds. + **Returns** -- [int]# +- [void]# --- -### setTime {#clock-set-time} - -Added in: v1.45clock.setTime +### setFixedTime {#clock-set-fixed-time} -Set the clock to the specified time. +Added in: v1.45clock.setFixedTime -When fake timers are installed, only fires timers at most once. This can be used to simulate the JS engine (such as a browser) being put to sleep and resumed later, skipping intermediary timers. +Makes `Date.now` and `new Date()` return fixed fake time at all times, keeps all the timers running. **Usage** ```java -Clock.setTime(time); +page.clock().setFixedTime(Instant.now()); +page.clock().setFixedTime(Instant.parse("2020-02-02")); +page.clock().setFixedTime("2020-02-02"); ``` **Arguments** -- `time` [int] | [Date]# +- `time` [int] | [String] | [Date]# + + Time to be set. **Returns** -- [void]# +- [void]# --- -### skipTime {#clock-skip-time} +### setSystemTime {#clock-set-system-time} -Added in: v1.45clock.skipTime +Added in: v1.45clock.setSystemTime -Advance the clock by jumping forward in time, equivalent to running [Clock.setTime()](/api/class-clock.mdx#clock-set-time) with the new target time. - -When fake timers are installed, [Clock.skipTime()](/api/class-clock.mdx#clock-skip-time) only fires due timers at most once, while [Clock.runFor()](/api/class-clock.mdx#clock-run-for) fires all the timers up to the current time. Returns fake milliseconds since the unix epoch. +Sets current system time but does not trigger any timers. **Usage** ```java -page.clock().skipTime(1000); -page.clock().skipTime("30:00"); +page.clock().setSystemTime(Instant.now()); +page.clock().setSystemTime(Instant.parse("2020-02-02")); +page.clock().setSystemTime("2020-02-02"); ``` **Arguments** -- `time` [int] | [String]# - - Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds. +- `time` [int] | [String] | [Date]# **Returns** -- [int]# +- [void]# [APIRequest]: /api/class-apirequest.mdx "APIRequest" diff --git a/java/docs/clock.mdx b/java/docs/clock.mdx index 0106344f18..5748b0b01d 100644 --- a/java/docs/clock.mdx +++ b/java/docs/clock.mdx @@ -20,63 +20,80 @@ Accurately simulating time-dependent behavior is essential for verifying the cor - `cancelAnimationFrame` - `requestIdleCallback` - `cancelIdleCallback` +- `performance` -By default, the clock starts at the unix epoch (timestamp of 0). You can override it using the `now` option. +## Test with predefined time -## Mock Date.now - -Most of the time, you only need to fake `Date.now` and no other time-related functions. That way the time flows naturally, but `Date.now` returns a fixed value. +Often you only need to fake `Date.now` while keeping the timers going. That way the time flows naturally, but `Date.now` always returns a fixed value. ```html
``` -```java -page.clock().setTime(Instant.parse("2024-02-02T10:00:00")); -page.navigate("http://localhost:3333"); -Locator locator = page.getByTestId("current-time"); -assertThat(locator).hasText("2/2/2024, 10:00:00 AM"); - -page.clock().setTime(Instant.parse("2024-02-02T10:30:00")); -assertThat(locator).hasText("2/2/2024, 10:30:00 AM"); -``` - -## Mock Date.now consistent with the timers +## Consistent time and timers -Sometimes your timers depend on `Date.now` and are confused when the time stands still. In cases like this you need to ensure that `Date.now` and timers are consistent. You can achieve this by installing the fake timers. +Sometimes your timers depend on `Date.now` and are confused when the `Date.now` value does not change over time. In this case, you can install the clock and fast forward to the time of interest when testing. ```html
``` ```java -// Initialize clock with a specific time, take full control over time. -page.clock().installFakeTimers(Instant.parse("2024-02-02T10:00:00")); +// Initialize clock with some time before the test time and let the page load +// naturally. `Date.now` will progress as the timers fire. +page.clock().install(new Clock.InstallOptions().setTime(Instant.parse("2024-02-02T08:00:00"))); page.navigate("http://localhost:3333"); Locator locator = page.getByTestId("current-time"); -assertThat(locator).hasText("2/2/2024, 10:00:00 AM") -// Fast forward time 30 minutes without firing intermediate timers, as if the user -// closed and opened the lid of the laptop. -page.clock().skipTime("30:00"); +// Pretend that the user closed the laptop lid and opened it again at 10am. +// Pause the time once reached that point. +page.clock().pauseAt(Instant.parse("2024-02-02T10:00:00")); + +// Assert the page state. +assertThat(locator).hasText("2/2/2024, 10:00:00 AM"); + +// Close the laptop lid again and open it at 10:30am. +page.clock().fastForward("30:00"); assertThat(locator).hasText("2/2/2024, 10:30:00 AM"); ``` -## Tick through time manually +## Test inactivity monitoring + +Inactivity monitoring is a common feature in web applications that logs out users after a period of inactivity. Testing this feature can be tricky because you need to wait for a long time to see the effect. With the help of the clock, you can speed up time and test this feature quickly. + +```java +// Initial time does not matter for the test, so we can pick current time. +page.clock().install(); +page.navigate("http://localhost:3333"); +Locator locator = page.getByRole("button"); + +// Interact with the page +locator.click(); + +// Fast forward time 5 minutes as if the user did not do anything. +// Fast forward is like closing the laptop lid and opening it after 5 minutes. +// All the timers due will fire once immediately, as in the real browser. +page.clock().fastForward("5:00"); + +// Check that the user was logged out automatically. +assertThat(page.getByText("You have been logged out due to inactivity.")).isVisible(); +``` + +## Tick through time manually, firing all the timers consistently In rare cases, you may want to tick through time manually, firing all timers and animation frames in the process to achieve a fine-grained control over the passage of time. @@ -85,18 +102,24 @@ In rare cases, you may want to tick through time manually, firing all timers and ``` ```java -// Initialize clock with a specific time, take full control over time. -page.clock().installFakeTimers(Instant.parse("2024-02-02T10:00:00")); +// Initialize clock with a specific time, let the page load naturally. +page.clock().install(new Clock.InstallOptions() + .setTime(Instant.parse("2024-02-02T08:00:00"))); page.navigate("http://localhost:3333"); Locator locator = page.getByTestId("current-time"); +// Pause the time flow, stop the timers, you now have manual control +// over the page time. +page.clock().pauseAt(Instant.parse("2024-02-02T10:00:00")); +assertThat(locator).hasText("2/2/2024, 10:00:00 AM"); + // Tick through time manually, firing all timers in the process. // In this case, time will be updated in the screen 2 times. page.clock().runFor(2000); diff --git a/java/docs/languages.mdx b/java/docs/languages.mdx index eec74f1dc2..8347cf632c 100644 --- a/java/docs/languages.mdx +++ b/java/docs/languages.mdx @@ -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) diff --git a/nodejs/docs/api/class-browsercontext.mdx b/nodejs/docs/api/class-browsercontext.mdx index 90c4741586..c399ca3fb6 100644 --- a/nodejs/docs/api/class-browsercontext.mdx +++ b/nodejs/docs/api/class-browsercontext.mdx @@ -430,21 +430,22 @@ await browserContext.grantPermissions(permissions, options); - `permissions` [Array]<[string]># A permission or an array of permissions to grant. Permissions can be one of the following values: - * `'geolocation'` - * `'midi'` - * `'midi-sysex'` (system-exclusive midi) - * `'notifications'` - * `'camera'` - * `'microphone'` - * `'background-sync'` - * `'ambient-light-sensor'` * `'accelerometer'` - * `'gyroscope'` - * `'magnetometer'` * `'accessibility-events'` + * `'ambient-light-sensor'` + * `'background-sync'` + * `'camera'` * `'clipboard-read'` * `'clipboard-write'` + * `'geolocation'` + * `'gyroscope'` + * `'magnetometer'` + * `'microphone'` + * `'midi-sysex'` (system-exclusive midi) + * `'midi'` + * `'notifications'` * `'payment-handler'` + * `'storage-access'` - `options` [Object] *(optional)* - `origin` [string] *(optional)*# diff --git a/nodejs/docs/api/class-clock.mdx b/nodejs/docs/api/class-clock.mdx index 3196d2e038..6e429e96f5 100644 --- a/nodejs/docs/api/class-clock.mdx +++ b/nodejs/docs/api/class-clock.mdx @@ -16,11 +16,35 @@ Note that clock is installed for the entire [BrowserContext], so the time in all ## Methods -### installFakeTimers {#clock-install-fake-timers} +### fastForward {#clock-fast-forward} -Added in: v1.45clock.installFakeTimers +Added in: v1.45clock.fastForward + +Advance the clock by jumping forward in time. Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and reopening it later, after given time. + +**Usage** + +```js +await page.clock.fastForward(1000); +await page.clock.fastForward('30:00'); +``` + +**Arguments** +- `ticks` [number] | [string]# + + Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds. + +**Returns** +- [Promise]<[void]># + +--- + +### install {#clock-install} + +Added in: v1.45clock.install Install fake implementations for the following time-related functions: +* `Date` * `setTimeout` * `clearTimeout` * `setInterval` @@ -31,151 +55,132 @@ Install fake implementations for the following time-related functions: * `cancelIdleCallback` * `performance` -Fake timers are used to manually control the flow of time in tests. They allow you to advance time, fire timers, and control the behavior of time-dependent functions. See [clock.runFor()](/api/class-clock.mdx#clock-run-for) and [clock.skipTime()](/api/class-clock.mdx#clock-skip-time) for more information. +Fake timers are used to manually control the flow of time in tests. They allow you to advance time, fire timers, and control the behavior of time-dependent functions. See [clock.runFor()](/api/class-clock.mdx#clock-run-for) and [clock.fastForward()](/api/class-clock.mdx#clock-fast-forward) for more information. **Usage** ```js -await clock.installFakeTimers(time); -await clock.installFakeTimers(time, options); +await clock.install(); +await clock.install(options); ``` **Arguments** -- `time` [number] | [Date]# - - Install fake timers with the specified base time. - `options` [Object] *(optional)* - - `loopLimit` [number] *(optional)*# + - `time` [number] | [string] | [Date] *(optional)*# - The maximum number of timers that will be run in [clock.runAllTimers()](/api/class-clock.mdx#clock-run-all-timers). Defaults to `1000`. + Time to initialize with, current system time by default. **Returns** -- [Promise]<[void]># +- [Promise]<[void]># --- -### runAllTimers {#clock-run-all-timers} - -Added in: v1.45clock.runAllTimers - -Runs all pending timers until there are none remaining. If new timers are added while it is executing they will be run as well. Fake timers must be installed. Returns fake milliseconds since the unix epoch. +### pauseAt {#clock-pause-at} -**Usage** - -```js -await clock.runAllTimers(); -``` - -**Returns** -- [Promise]<[number]># +Added in: v1.45clock.pauseAt -**Details** +Advance the clock by jumping forward in time and pause the time. Once this method is called, no timers are fired unless [clock.runFor()](/api/class-clock.mdx#clock-run-for), [clock.fastForward()](/api/class-clock.mdx#clock-fast-forward), [clock.pauseAt()](/api/class-clock.mdx#clock-pause-at) or [clock.resume()](/api/class-clock.mdx#clock-resume) is called. -This makes it easier to run asynchronous tests to completion without worrying about the number of timers they use, or the delays in those timers. It runs a maximum of `loopLimit` times after which it assumes there is an infinite loop of timers and throws an error. - ---- - -### runFor {#clock-run-for} - -Added in: v1.45clock.runFor - -Advance the clock, firing callbacks if necessary. Returns fake milliseconds since the unix epoch. Fake timers must be installed. Returns fake milliseconds since the unix epoch. +Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and reopening it at the specified time and pausing. **Usage** ```js -await page.clock.runFor(1000); -await page.clock.runFor('30:00'); +await page.clock.pauseAt(new Date('2020-02-02')); +await page.clock.pauseAt('2020-02-02'); ``` **Arguments** -- `time` [number] | [string]# - - Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds. +- `time` [number] | [string] | [Date]# **Returns** -- [Promise]<[number]># +- [Promise]<[void]># --- -### runToLastTimer {#clock-run-to-last-timer} +### resume {#clock-resume} -Added in: v1.45clock.runToLastTimer +Added in: v1.45clock.resume -This takes note of the last scheduled timer when it is run, and advances the clock to that time firing callbacks as necessary. If new timers are added while it is executing they will be run only if they would occur before this time. This is useful when you want to run a test to completion, but the test recursively sets timers that would cause runAll to trigger an infinite loop warning. Fake timers must be installed. Returns fake milliseconds since the unix epoch. +Resumes timers. Once this method is called, time resumes flowing, timers are fired as usual. **Usage** ```js -await clock.runToLastTimer(); +await clock.resume(); ``` **Returns** -- [Promise]<[number]># +- [Promise]<[void]># --- -### runToNextTimer {#clock-run-to-next-timer} +### runFor {#clock-run-for} -Added in: v1.45clock.runToNextTimer +Added in: v1.45clock.runFor -Advances the clock to the moment of the first scheduled timer, firing it. Fake timers must be installed. Returns fake milliseconds since the unix epoch. +Advance the clock, firing all the time-related callbacks. **Usage** ```js -await clock.runToNextTimer(); +await page.clock.runFor(1000); +await page.clock.runFor('30:00'); ``` +**Arguments** +- `ticks` [number] | [string]# + + Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds. + **Returns** -- [Promise]<[number]># +- [Promise]<[void]># --- -### setTime {#clock-set-time} - -Added in: v1.45clock.setTime +### setFixedTime {#clock-set-fixed-time} -Set the clock to the specified time. +Added in: v1.45clock.setFixedTime -When fake timers are installed, only fires timers at most once. This can be used to simulate the JS engine (such as a browser) being put to sleep and resumed later, skipping intermediary timers. +Makes `Date.now` and `new Date()` return fixed fake time at all times, keeps all the timers running. **Usage** ```js -await clock.setTime(time); +await page.clock.setFixedTime(Date.now()); +await page.clock.setFixedTime(new Date('2020-02-02')); +await page.clock.setFixedTime('2020-02-02'); ``` **Arguments** -- `time` [number] | [Date]# +- `time` [number] | [string] | [Date]# + + Time to be set. **Returns** -- [Promise]<[void]># +- [Promise]<[void]># --- -### skipTime {#clock-skip-time} +### setSystemTime {#clock-set-system-time} -Added in: v1.45clock.skipTime +Added in: v1.45clock.setSystemTime -Advance the clock by jumping forward in time, equivalent to running [clock.setTime()](/api/class-clock.mdx#clock-set-time) with the new target time. - -When fake timers are installed, [clock.skipTime()](/api/class-clock.mdx#clock-skip-time) only fires due timers at most once, while [clock.runFor()](/api/class-clock.mdx#clock-run-for) fires all the timers up to the current time. Returns fake milliseconds since the unix epoch. +Sets current system time but does not trigger any timers. **Usage** ```js -await page.clock.skipTime(1000); -await page.clock.skipTime('30:00'); +await page.clock.setSystemTime(Date.now()); +await page.clock.setSystemTime(new Date('2020-02-02')); +await page.clock.setSystemTime('2020-02-02'); ``` **Arguments** -- `time` [number] | [string]# - - Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds. +- `time` [number] | [string] | [Date]# **Returns** -- [Promise]<[number]># +- [Promise]<[void]># [Accessibility]: /api/class-accessibility.mdx "Accessibility" diff --git a/nodejs/docs/api/class-test.mdx b/nodejs/docs/api/class-test.mdx index c8ab768151..364fce6270 100644 --- a/nodejs/docs/api/class-test.mdx +++ b/nodejs/docs/api/class-test.mdx @@ -81,8 +81,7 @@ import { test, expect } from '@playwright/test'; test('basic test', { annotation: { type: 'issue', - description: 'feature tags API', - url: 'https://github.com/microsoft/playwright/issues/23180' + description: 'https://github.com/microsoft/playwright/issues/23180', }, }, async ({ page }) => { await page.goto('https://playwright.dev/'); @@ -110,10 +109,7 @@ Learn more about [test annotations](../test-annotations.mdx). Annotation type, for example `'issue'`. - `description` [string] *(optional)* - Optional annotation description. - - `url` [string] *(optional)* - - Optional for example an issue url. + Optional annotation description, for example an issue url. Additional test details. @@ -438,9 +434,6 @@ Learn more about [test annotations](../test-annotations.mdx). - `description` [string] *(optional)* - - `url` [string] *(optional)* - - Additional details for all tests in the group. - `callback` [function]# @@ -563,9 +556,6 @@ test.describe.fixme(() => { - `description` [string] *(optional)* - - `url` [string] *(optional)* - - See [test.describe()](/api/class-test.mdx#test-describe) for details description. - `callback` [function]# @@ -619,9 +609,6 @@ test.describe.only(() => { - `description` [string] *(optional)* - - `url` [string] *(optional)* - - See [test.describe()](/api/class-test.mdx#test-describe) for details description. - `callback` [function]# @@ -672,9 +659,6 @@ test.describe.skip(() => { - `description` [string] *(optional)* - - `url` [string] *(optional)* - - See [test.describe()](/api/class-test.mdx#test-describe) for details description. - `callback` [function]# @@ -910,9 +894,6 @@ test('less readable', async ({ page }) => { - `description` [string] *(optional)* - - `url` [string] *(optional)* - - See [test()](/api/class-test.mdx#test-call) for test details description. - `body` [function]\([Fixtures], [TestInfo]\) *(optional)* Added in: v1.42# @@ -1009,9 +990,6 @@ test('less readable', async ({ page }) => { - `description` [string] *(optional)* - - `url` [string] *(optional)* - - See [test()](/api/class-test.mdx#test-call) for test details description. - `body` [function]\([Fixtures], [TestInfo]\) *(optional)*# @@ -1083,9 +1061,6 @@ test.only('focus this test', async ({ page }) => { - `description` [string] *(optional)* - - `url` [string] *(optional)* - - See [test()](/api/class-test.mdx#test-call) for test details description. - `body` [function]\([Fixtures], [TestInfo]\)# @@ -1231,9 +1206,6 @@ test('less readable', async ({ page }) => { - `description` [string] *(optional)* - - `url` [string] *(optional)* - - See [test()](/api/class-test.mdx#test-call) for test details description. - `body` [function]\([Fixtures], [TestInfo]\) *(optional)*# @@ -1604,9 +1576,6 @@ test.describe.parallel(() => { - `description` [string] *(optional)* - - `url` [string] *(optional)* - - See [test.describe()](/api/class-test.mdx#test-describe) for details description. - `callback` [function]# @@ -1663,9 +1632,6 @@ test.describe.parallel.only(() => { - `description` [string] *(optional)* - - `url` [string] *(optional)* - - See [test.describe()](/api/class-test.mdx#test-describe) for details description. - `callback` [function]# @@ -1727,9 +1693,6 @@ test.describe.serial(() => { - `description` [string] *(optional)* - - `url` [string] *(optional)* - - See [test.describe()](/api/class-test.mdx#test-describe) for details description. - `callback` [function]# @@ -1793,9 +1756,6 @@ test.describe.serial.only(() => { - `description` [string] *(optional)* - - `url` [string] *(optional)* - - See [test.describe()](/api/class-test.mdx#test-describe) for details description. - `callback` [function]# diff --git a/nodejs/docs/api/class-testconfig.mdx b/nodejs/docs/api/class-testconfig.mdx index 238bd5d088..2828be61dc 100644 --- a/nodejs/docs/api/class-testconfig.mdx +++ b/nodejs/docs/api/class-testconfig.mdx @@ -628,31 +628,6 @@ export default defineConfig({ --- -### shardingSeed {#test-config-sharding-seed} - -Added in: v1.45testConfig.shardingSeed - -Shuffle the order of test groups with a seed. By default tests are run in the order they are discovered, which is mostly alphabetical. This could lead to an uneven distribution of slow and fast tests. Shuffling the order of tests in a deterministic way can help to distribute the load more evenly. - -The sharding seed is a string that is used to initialize a random number generator. - -Learn more about [parallelism and sharding](../test-parallel.mdx) with Playwright Test. - -**Usage** - -```js title="playwright.config.ts" -import { defineConfig } from '@playwright/test'; - -export default defineConfig({ - shardingSeed: 'string value' -}); -``` - -**Type** -- [string] - ---- - ### snapshotPathTemplate {#test-config-snapshot-path-template} Added in: v1.28testConfig.snapshotPathTemplate diff --git a/nodejs/docs/clock.mdx b/nodejs/docs/clock.mdx index 4390fa4982..86bf038e13 100644 --- a/nodejs/docs/clock.mdx +++ b/nodejs/docs/clock.mdx @@ -20,66 +20,87 @@ Accurately simulating time-dependent behavior is essential for verifying the cor - `cancelAnimationFrame` - `requestIdleCallback` - `cancelIdleCallback` +- `performance` -By default, the clock starts at the unix epoch (timestamp of 0). You can override it using the `now` option. +## Test with predefined time -```js -await page.clock.setTime(new Date('2020-02-02')); -await page.clock.installFakeTimers(new Date('2020-02-02')); -``` - -## Mock Date.now - -Most of the time, you only need to fake `Date.now` and no other time-related functions. That way the time flows naturally, but `Date.now` returns a fixed value. +Often you only need to fake `Date.now` while keeping the timers going. That way the time flows naturally, but `Date.now` always returns a fixed value. ```html
``` ```js -await page.clock.setTime(new Date('2024-02-02T10:00:00')); +await page.clock.setFixedTime(new Date('2024-02-02T10:00:00')); await page.goto('http://localhost:3333'); await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:00 AM'); -await page.clock.setTime(new Date('2024-02-02T10:30:00')); +await page.clock.setFixedTime(new Date('2024-02-02T10:30:00')); +// We know that the page has a timer that updates the time every second. await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:30:00 AM'); ``` -## Mock Date.now consistent with the timers +## Consistent time and timers -Sometimes your timers depend on `Date.now` and are confused when the time stands still. In cases like this you need to ensure that `Date.now` and timers are consistent. You can achieve this by installing the fake timers. +Sometimes your timers depend on `Date.now` and are confused when the `Date.now` value does not change over time. In this case, you can install the clock and fast forward to the time of interest when testing. ```html
``` ```js -// Initialize clock with a specific time, take full control over time. -await page.clock.installFakeTimers(new Date('2024-02-02T10:00:00')); +// Initialize clock with some time before the test time and let the page load +// naturally. `Date.now` will progress as the timers fire. +await page.clock.install({ time: new Date('2024-02-02T08:00:00') }); await page.goto('http://localhost:3333'); + +// Pretend that the user closed the laptop lid and opened it again at 10am, +// Pause the time once reached that point. +await page.clock.pauseAt(new Date('2024-02-02T10:00:00')); + +// Assert the page state. await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:00 AM'); -// Fast forward time 30 minutes without firing intermediate timers, as if the user -// closed and opened the lid of the laptop. -await page.clock.skipTime('30:00'); +// Close the laptop lid again and open it at 10:30am. +await page.clock.fastForward('30:00'); await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:30:00 AM'); ``` -## Tick through time manually +## Test inactivity monitoring + +Inactivity monitoring is a common feature in web applications that logs out users after a period of inactivity. Testing this feature can be tricky because you need to wait for a long time to see the effect. With the help of the clock, you can speed up time and test this feature quickly. + +```js +// Initial time does not matter for the test, so we can pick current time. +await page.clock.install(); +await page.goto('http://localhost:3333'); +// Interact with the page +await page.getByRole('button').click(); + +// Fast forward time 5 minutes as if the user did not do anything. +// Fast forward is like closing the laptop lid and opening it after 5 minutes. +// All the timers due will fire once immediately, as in the real browser. +await page.clock.fastForward('5:00'); + +// Check that the user was logged out automatically. +await expect(page.getByText('You have been logged out due to inactivity.')).toBeVisible(); +``` + +## Tick through time manually, firing all the timers consistently In rare cases, you may want to tick through time manually, firing all timers and animation frames in the process to achieve a fine-grained control over the passage of time. @@ -88,21 +109,26 @@ In rare cases, you may want to tick through time manually, firing all timers and ``` ```js -// Initialize clock with a specific time, take full control over time. -await page.clock.installFakeTimers(new Date('2024-02-02T10:00:00')); +// Initialize clock with a specific time, let the page load naturally. +await page.clock.install({ time: new Date('2024-02-02T08:00:00') }); await page.goto('http://localhost:3333'); +// Pause the time flow, stop the timers, you now have manual control +// over the page time. +await page.clock.pauseAt(new Date('2024-02-02T10:00:00')); +await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:00 AM'); + // Tick through time manually, firing all timers in the process. // In this case, time will be updated in the screen 2 times. await page.clock.runFor(2000); -await expect(locator).to_have_text('2/2/2024, 10:00:02 AM'); +await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:02 AM'); ``` diff --git a/nodejs/docs/languages.mdx b/nodejs/docs/languages.mdx index 23c51cbf11..35826e6b31 100644 --- a/nodejs/docs/languages.mdx +++ b/nodejs/docs/languages.mdx @@ -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) diff --git a/nodejs/docs/test-sharding.mdx b/nodejs/docs/test-sharding.mdx index 78a70d56c2..b8c9b42237 100644 --- a/nodejs/docs/test-sharding.mdx +++ b/nodejs/docs/test-sharding.mdx @@ -25,12 +25,6 @@ Now, if you run these shards in parallel on different computers, your test suite Note that Playwright can only shard tests that can be run in parallel. By default, this means Playwright will shard test files. Learn about other options in the [parallelism guide](./test-parallel.mdx). -## Randomizing test order in a deterministic way - -By default tests are run in the order they are discovered, which is mostly alphabetical. This could lead to an uneven distribution of slow and fast tests. For example, if the first half of your tests are slower than the rest of your tests and you are using 4 shards it means that shard 1 and 2 will take significantly more time then shard 3 and 4. - -To aid with this problem you can pass `--sharding-seed=string-value` to randomize the order of tests in a deterministic way, which could yield better distribution of slow and fast tests across all shards. - ## Merging reports from multiple shards In the previous example, each test shard has its own test report. If you want to have a combined report showing all the test results from all the shards, you can merge them. diff --git a/python/docs/api/class-browsercontext.mdx b/python/docs/api/class-browsercontext.mdx index 7fade8b8ff..d0b4cb599b 100644 --- a/python/docs/api/class-browsercontext.mdx +++ b/python/docs/api/class-browsercontext.mdx @@ -732,21 +732,22 @@ browser_context.grant_permissions(permissions, **kwargs) - `permissions` [List]\[[str]\]# A permission or an array of permissions to grant. Permissions can be one of the following values: - * `'geolocation'` - * `'midi'` - * `'midi-sysex'` (system-exclusive midi) - * `'notifications'` - * `'camera'` - * `'microphone'` - * `'background-sync'` - * `'ambient-light-sensor'` * `'accelerometer'` - * `'gyroscope'` - * `'magnetometer'` * `'accessibility-events'` + * `'ambient-light-sensor'` + * `'background-sync'` + * `'camera'` * `'clipboard-read'` * `'clipboard-write'` + * `'geolocation'` + * `'gyroscope'` + * `'magnetometer'` + * `'microphone'` + * `'midi-sysex'` (system-exclusive midi) + * `'midi'` + * `'notifications'` * `'payment-handler'` + * `'storage-access'` - `origin` [str] *(optional)*# The [origin] to grant permissions to, e.g. "https://example.com". diff --git a/python/docs/api/class-clock.mdx b/python/docs/api/class-clock.mdx index 66e36179a5..d28ae3a4b6 100644 --- a/python/docs/api/class-clock.mdx +++ b/python/docs/api/class-clock.mdx @@ -16,11 +16,56 @@ Note that clock is installed for the entire [BrowserContext], so the time in all ## Methods -### install_fake_timers {#clock-install-fake-timers} +### fast_forward {#clock-fast-forward} -Added in: v1.45clock.install_fake_timers +Added in: v1.45clock.fast_forward + +Advance the clock by jumping forward in time. Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and reopening it later, after given time. + +**Usage** + + + + +```py +page.clock.fast_forward(1000) +page.clock.fast_forward("30:00") +``` + + + + +```py +await page.clock.fast_forward(1000) +await page.clock.fast_forward("30:00") +``` + + + + +**Arguments** +- `ticks` [int] | [str]# + + Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds. + +**Returns** +- [NoneType]# + +--- + +### install {#clock-install} + +Added in: v1.45clock.install Install fake implementations for the following time-related functions: +* `Date` * `setTimeout` * `clearTimeout` * `setInterval` @@ -31,54 +76,32 @@ Install fake implementations for the following time-related functions: * `cancelIdleCallback` * `performance` -Fake timers are used to manually control the flow of time in tests. They allow you to advance time, fire timers, and control the behavior of time-dependent functions. See [clock.run_for()](/api/class-clock.mdx#clock-run-for) and [clock.skip_time()](/api/class-clock.mdx#clock-skip-time) for more information. +Fake timers are used to manually control the flow of time in tests. They allow you to advance time, fire timers, and control the behavior of time-dependent functions. See [clock.run_for()](/api/class-clock.mdx#clock-run-for) and [clock.fast_forward()](/api/class-clock.mdx#clock-fast-forward) for more information. **Usage** ```python -clock.install_fake_timers(time) -clock.install_fake_timers(time, **kwargs) +clock.install() +clock.install(**kwargs) ``` **Arguments** -- `time` [int] | [Date]# +- `time` [int] | [str] | [Date] *(optional)*# - Install fake timers with the specified base time. -- `loop_limit` [int] *(optional)*# - - The maximum number of timers that will be run in [clock.run_all_timers()](/api/class-clock.mdx#clock-run-all-timers). Defaults to `1000`. + Time to initialize with, current system time by default. **Returns** -- [NoneType]# +- [NoneType]# --- -### run_all_timers {#clock-run-all-timers} - -Added in: v1.45clock.run_all_timers +### pause_at {#clock-pause-at} -Runs all pending timers until there are none remaining. If new timers are added while it is executing they will be run as well. Fake timers must be installed. Returns fake milliseconds since the unix epoch. +Added in: v1.45clock.pause_at -**Usage** +Advance the clock by jumping forward in time and pause the time. Once this method is called, no timers are fired unless [clock.run_for()](/api/class-clock.mdx#clock-run-for), [clock.fast_forward()](/api/class-clock.mdx#clock-fast-forward), [clock.pause_at()](/api/class-clock.mdx#clock-pause-at) or [clock.resume()](/api/class-clock.mdx#clock-resume) is called. -```python -clock.run_all_timers() -``` - -**Returns** -- [int]# - -**Details** - -This makes it easier to run asynchronous tests to completion without worrying about the number of timers they use, or the delays in those timers. It runs a maximum of `loop_limit` times after which it assumes there is an infinite loop of timers and throws an error. - ---- - -### run_for {#clock-run-for} - -Added in: v1.45clock.run_for - -Advance the clock, firing callbacks if necessary. Returns fake milliseconds since the unix epoch. Fake timers must be installed. Returns fake milliseconds since the unix epoch. +Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and reopening it at the specified time and pausing. **Usage** @@ -93,94 +116,141 @@ Advance the clock, firing callbacks if necessary. Returns fake milliseconds sinc ```py -page.clock.run_for(1000); -page.clock.run_for('30:00') +page.clock.pause_at(datetime.datetime(2020, 2, 2)) +page.clock.pause_at("2020-02-02") ``` ```py -await page.clock.run_for(1000); -await page.clock.run_for('30:00') +await page.clock.pause_at(datetime.datetime(2020, 2, 2)) +await page.clock.pause_at("2020-02-02") ```
**Arguments** -- `time` [int] | [str]# - - Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds. +- `time` [int] | [str] | [Date]# **Returns** -- [int]# +- [NoneType]# --- -### run_to_last_timer {#clock-run-to-last-timer} +### resume {#clock-resume} -Added in: v1.45clock.run_to_last_timer +Added in: v1.45clock.resume -This takes note of the last scheduled timer when it is run, and advances the clock to that time firing callbacks as necessary. If new timers are added while it is executing they will be run only if they would occur before this time. This is useful when you want to run a test to completion, but the test recursively sets timers that would cause runAll to trigger an infinite loop warning. Fake timers must be installed. Returns fake milliseconds since the unix epoch. +Resumes timers. Once this method is called, time resumes flowing, timers are fired as usual. **Usage** ```python -clock.run_to_last_timer() +clock.resume() ``` **Returns** -- [int]# +- [NoneType]# --- -### run_to_next_timer {#clock-run-to-next-timer} +### run_for {#clock-run-for} -Added in: v1.45clock.run_to_next_timer +Added in: v1.45clock.run_for -Advances the clock to the moment of the first scheduled timer, firing it. Fake timers must be installed. Returns fake milliseconds since the unix epoch. +Advance the clock, firing all the time-related callbacks. **Usage** -```python -clock.run_to_next_timer() + + + +```py +page.clock.run_for(1000); +page.clock.run_for("30:00") +``` + + + + +```py +await page.clock.run_for(1000); +await page.clock.run_for("30:00") ``` + + + +**Arguments** +- `ticks` [int] | [str]# + + Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds. + **Returns** -- [int]# +- [NoneType]# --- -### set_time {#clock-set-time} - -Added in: v1.45clock.set_time +### set_fixed_time {#clock-set-fixed-time} -Set the clock to the specified time. +Added in: v1.45clock.set_fixed_time -When fake timers are installed, only fires timers at most once. This can be used to simulate the JS engine (such as a browser) being put to sleep and resumed later, skipping intermediary timers. +Makes `Date.now` and `new Date()` return fixed fake time at all times, keeps all the timers running. **Usage** -```python -clock.set_time(time) + + + +```py +page.clock.set_fixed_time(datetime.datetime.now()) +page.clock.set_fixed_time(datetime.datetime(2020, 2, 2)) +page.clock.set_fixed_time("2020-02-02") ``` + + + +```py +await page.clock.set_fixed_time(datetime.datetime.now()) +await page.clock.set_fixed_time(datetime.datetime(2020, 2, 2)) +await page.clock.set_fixed_time("2020-02-02") +``` + + + + **Arguments** -- `time` [int] | [Date]# +- `time` [int] | [str] | [Date]# + + Time to be set. **Returns** -- [NoneType]# +- [NoneType]# --- -### skip_time {#clock-skip-time} +### set_system_time {#clock-set-system-time} -Added in: v1.45clock.skip_time +Added in: v1.45clock.set_system_time -Advance the clock by jumping forward in time, equivalent to running [clock.set_time()](/api/class-clock.mdx#clock-set-time) with the new target time. - -When fake timers are installed, [clock.skip_time()](/api/class-clock.mdx#clock-skip-time) only fires due timers at most once, while [clock.run_for()](/api/class-clock.mdx#clock-run-for) fires all the timers up to the current time. Returns fake milliseconds since the unix epoch. +Sets current system time but does not trigger any timers. **Usage** @@ -195,28 +265,28 @@ When fake timers are installed, [clock.skip_time()](/api/class-clock.mdx#clock-s ```py -page.clock.skipTime(1000); -page.clock.skipTime('30:00') +page.clock.set_system_time(datetime.datetime.now()) +page.clock.set_system_time(datetime.datetime(2020, 2, 2)) +page.clock.set_system_time("2020-02-02") ``` ```py -await page.clock.skipTime(1000); -await page.clock.skipTime('30:00') +await page.clock.set_system_time(datetime.datetime.now()) +await page.clock.set_system_time(datetime.datetime(2020, 2, 2)) +await page.clock.set_system_time("2020-02-02") ```
**Arguments** -- `time` [int] | [str]# - - Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds. +- `time` [int] | [str] | [Date]# **Returns** -- [int]# +- [NoneType]# [Accessibility]: /api/class-accessibility.mdx "Accessibility" diff --git a/python/docs/clock.mdx b/python/docs/clock.mdx index 4b350568c4..71480ed93e 100644 --- a/python/docs/clock.mdx +++ b/python/docs/clock.mdx @@ -20,19 +20,33 @@ Accurately simulating time-dependent behavior is essential for verifying the cor - `cancelAnimationFrame` - `requestIdleCallback` - `cancelIdleCallback` +- `performance` -By default, the clock starts at the unix epoch (timestamp of 0). You can override it using the `now` option. +## Test with predefined time -## Mock Date.now +Often you only need to fake `Date.now` while keeping the timers going. That way the time flows naturally, but `Date.now` always returns a fixed value. -Most of the time, you only need to fake `Date.now` and no other time-related functions. That way the time flows naturally, but `Date.now` returns a fixed value. +```html +
+ +``` + +## Consistent time and timers + +Sometimes your timers depend on `Date.now` and are confused when the `Date.now` value does not change over time. In this case, you can install the clock and fast forward to the time of interest when testing. ```html
@@ -49,45 +63,50 @@ Most of the time, you only need to fake `Date.now` and no other time-related fun ```py -page.clock.set_time(datetime.datetime(2024, 2, 2, 10, 0, 0, tzinfo=datetime.timezone.pst)) -page.goto('http://localhost:3333') -locator = page.get_by_test_id('current-time') -expect(locator).to_have_text('2/2/2024, 10:00:00 AM') +# Initialize clock with some time before the test time and let the page load +# naturally. `Date.now` will progress as the timers fire. +page.clock.install(time=datetime.datetime(2024, 2, 2, 8, 0, 0)) +page.goto("http://localhost:3333") + +# Pretend that the user closed the laptop lid and opened it again at 10am. +# Pause the time once reached that point. +page.clock.pause_at(datetime.datetime(2024, 2, 2, 10, 0, 0)) -page.clock.set_time(datetime.datetime(2024, 2, 2, 10, 30, 0, tzinfo=datetime.timezone.pst)) -expect(locator).to_have_text('2/2/2024, 10:30:00 AM') +# Assert the page state. +expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:00:00 AM") + +# Close the laptop lid again and open it at 10:30am. +page.clock.fast_forward("30:00") +expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:30:00 AM") ``` ```py -page.clock.set_time(datetime.datetime(2024, 2, 2, 10, 0, 0, tzinfo=datetime.timezone.pst)) -await page.goto('http://localhost:3333') -locator = page.get_by_test_id('current-time') -await expect(locator).to_have_text('2/2/2024, 10:00:00 AM') +# Initialize clock with some time before the test time and let the page load +# naturally. `Date.now` will progress as the timers fire. +await page.clock.install(time=datetime.datetime(2024, 2, 2, 8, 0, 0)) +await page.goto("http://localhost:3333") + +# Pretend that the user closed the laptop lid and opened it again at 10am. +# Pause the time once reached that point. +await page.clock.pause_at(datetime.datetime(2024, 2, 2, 10, 0, 0)) + +# Assert the page state. +await expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:00:00 AM") -page.clock.set_time(datetime.datetime(2024, 2, 2, 10, 30, 0, tzinfo=datetime.timezone.pst)) -await expect(locator).to_have_text('2/2/2024, 10:30:00 AM') +# Close the laptop lid again and open it at 10:30am. +await page.clock.fast_forward("30:00") +await expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:30:00 AM") ```
-## Mock Date.now consistent with the timers +## Test inactivity monitoring -Sometimes your timers depend on `Date.now` and are confused when the time stands still. In cases like this you need to ensure that `Date.now` and timers are consistent. You can achieve this by installing the fake timers. - -```html -
- -``` +Inactivity monitoring is a common feature in web applications that logs out users after a period of inactivity. Testing this feature can be tricky because you need to wait for a long time to see the effect. With the help of the clock, you can speed up time and test this feature quickly. ```py -# Initialize clock with a specific time, take full control over time. -page.clock.install_fake_timers( - datetime.datetime(2024, 2, 2, 10, 0, 0, tzinfo=datetime.timezone.pst) -) -page.goto('http://localhost:3333') -locator = page.get_by_test_id('current-time') -expect(locator).to_have_text('2/2/2024, 10:00:00 AM') - -# Fast forward time 30 minutes without firing intermediate timers, as if the user -# closed and opened the lid of the laptop. -page.clock.skip_time('30:00') -expect(locator).to_have_text('2/2/2024, 10:30:00 AM') +# Initial time does not matter for the test, so we can pick current time. +page.clock.install() +page.goto("http://localhost:3333") +# Interact with the page +page.get_by_role("button").click() + +# Fast forward time 5 minutes as if the user did not do anything. +# Fast forward is like closing the laptop lid and opening it after 5 minutes. +# All the timers due will fire once immediately, as in the real browser. +page.clock.fast_forward("5:00") + +# Check that the user was logged out automatically. +expect(page.get_by_text("You have been logged out due to inactivity.")).to_be_visible() ```
```py -# Initialize clock with a specific time, take full control over time. -await page.clock.install_fake_timers( - datetime.datetime(2024, 2, 2, 10, 0, 0, tzinfo=datetime.timezone.pst) -) -await page.goto('http://localhost:3333') -locator = page.get_by_test_id('current-time') -await expect(locator).to_have_text('2/2/2024, 10:00:00 AM') - -# Fast forward time 30 minutes without firing intermediate timers, as if the user -# closed and opened the lid of the laptop. -await page.clock.skip_time('30:00') -await expect(locator).to_have_text('2/2/2024, 10:30:00 AM') +# Initial time does not matter for the test, so we can pick current time. +await page.clock.install() +await page.goto("http://localhost:3333") +# Interact with the page +await page.get_by_role("button").click() + +# Fast forward time 5 minutes as if the user did not do anything. +# Fast forward is like closing the laptop lid and opening it after 5 minutes. +# All the timers due will fire once immediately, as in the real browser. +await page.clock.fast_forward("5:00") + +# Check that the user was logged out automatically. +await expect(page.getByText("You have been logged out due to inactivity.")).toBeVisible() ```
-## Tick through time manually +## Tick through time manually, firing all the timers consistently In rare cases, you may want to tick through time manually, firing all timers and animation frames in the process to achieve a fine-grained control over the passage of time. @@ -144,7 +165,7 @@ In rare cases, you may want to tick through time manually, firing all timers and @@ -161,34 +182,44 @@ In rare cases, you may want to tick through time manually, firing all timers and ```py -# Initialize clock with a specific time, take full control over time. -page.clock.install_fake_timers( - datetime.datetime(2024, 2, 2, 10, 0, 0, tzinfo=datetime.timezone.pst), +# Initialize clock with a specific time, let the page load naturally. +page.clock.install( + time=datetime.datetime(2024, 2, 2, 8, 0, 0, tzinfo=datetime.timezone.pst), ) -page.goto('http://localhost:3333') -locator = page.get_by_test_id('current-time') +page.goto("http://localhost:3333") +locator = page.get_by_test_id("current-time") + +# Pause the time flow, stop the timers, you now have manual control +# over the page time. +page.clock.pause_at(datetime.datetime(2024, 2, 2, 10, 0, 0)) +expect(locator).to_have_text("2/2/2024, 10:00:00 AM") # Tick through time manually, firing all timers in the process. # In this case, time will be updated in the screen 2 times. page.clock.run_for(2000) -expect(locator).to_have_text('2/2/2024, 10:00:02 AM') +expect(locator).to_have_text("2/2/2024, 10:00:02 AM") ``` ```py -# Initialize clock with a specific time, take full control over time. -await page.clock.install_fake_timers( - datetime.datetime(2024, 2, 2, 10, 0, 0, tzinfo=datetime.timezone.pst), +# Initialize clock with a specific time, let the page load naturally. +await page.clock.install(time= + datetime.datetime(2024, 2, 2, 8, 0, 0, tzinfo=datetime.timezone.pst), ) -await page.goto('http://localhost:3333') -locator = page.get_by_test_id('current-time') +await page.goto("http://localhost:3333") +locator = page.get_by_test_id("current-time") + +# Pause the time flow, stop the timers, you now have manual control +# over the page time. +await page.clock.pause_at(datetime.datetime(2024, 2, 2, 10, 0, 0)) +await expect(locator).to_have_text("2/2/2024, 10:00:00 AM") # Tick through time manually, firing all timers in the process. # In this case, time will be updated in the screen 2 times. await page.clock.run_for(2000) -await expect(locator).to_have_text('2/2/2024, 10:00:02 AM') +await expect(locator).to_have_text("2/2/2024, 10:00:02 AM") ``` diff --git a/python/docs/languages.mdx b/python/docs/languages.mdx index eb749568b2..92a20fac1a 100644 --- a/python/docs/languages.mdx +++ b/python/docs/languages.mdx @@ -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)