/*
* MIT License
*
* Copyright (c) 2020 DarÃo Kondratiuk
* Copyright (c) 2020 Meir Blachman
* Modifications copyright (c) Microsoft Corporation.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Playwright.Helpers;
using Microsoft.Playwright.Transport.Protocol;
namespace Microsoft.Playwright.Core
{
internal class Locator : ILocator
{
internal readonly Frame _frame;
internal readonly string _selector;
private readonly LocatorLocatorOptions _options;
public Locator(Frame parent, string selector, LocatorLocatorOptions options = null)
{
_frame = parent;
_selector = selector;
_options = options;
if (options?.HasTextRegex != null)
{
_selector += $" >> :scope:text-matches({options.HasTextRegex.ToString().EscapeWithQuotes("\"")}, {options.HasTextRegex.Options.GetInlineFlags().EscapeWithQuotes("\"")})";
}
if (options?.HasTextString != null)
{
_selector += $" >> :scope:has-text({options.HasTextString.EscapeWithQuotes("\"")})";
}
if (options?.Has != null)
{
var has = (Locator)options.Has;
if (has._frame != _frame)
{
throw new ArgumentException("Inner \"has\" locator must belong to the same frame.");
}
_selector += " >> has=" + JsonSerializer.Serialize(has._selector);
}
}
public ILocator First => new Locator(_frame, $"{_selector} >> nth=0");
public ILocator Last => new Locator(_frame, $"{_selector} >> nth=-1");
IPage ILocator.Page => _frame.Page;
public async Task<IReadOnlyList<string>> AllInnerTextsAsync()
=> await EvaluateAllAsync<string[]>("ee => ee.map(e => e.innerText)").ConfigureAwait(false);
public async Task<IReadOnlyList<string>> AllTextContentsAsync()
=> await EvaluateAllAsync<string[]>("ee => ee.map(e => e.textContent || '')").ConfigureAwait(false);
public async Task<LocatorBoundingBoxResult> BoundingBoxAsync(LocatorBoundingBoxOptions options = null)
=> await WithElementAsync(
async (h, _) =>
{
var bb = await h.BoundingBoxAsync().ConfigureAwait(false);
if (bb == null)
{
return null;
}
return new LocatorBoundingBoxResult()
{
Height = bb.Height,
Width = bb.Width,
X = bb.X,
Y = bb.Y,
};
},
options).ConfigureAwait(false);
public Task CheckAsync(LocatorCheckOptions options = null)
=> _frame.CheckAsync(
_selector,
ConvertOptions<FrameCheckOptions>(options));
public Task ClickAsync(LocatorClickOptions options = null)
=> _frame.ClickAsync(
_selector,
ConvertOptions<FrameClickOptions>(options));
public Task SetCheckedAsync(bool checkedState, LocatorSetCheckedOptions options = null)
=> checkedState ?
CheckAsync(ConvertOptions<LocatorCheckOptions>(options))
: UncheckAsync(ConvertOptions<LocatorUncheckOptions>(options));
public Task<int> CountAsync()
=> _frame.QueryCountAsync(_selector);
public Task DblClickAsync(LocatorDblClickOptions options = null)
=> _frame.DblClickAsync(_selector, ConvertOptions<FrameDblClickOptions>(options));
public Task DispatchEventAsync(string type, object eventInit = null, LocatorDispatchEventOptions options = null)
=> _frame.DispatchEventAsync(_selector, type, eventInit, ConvertOptions<FrameDispatchEventOptions>(options));
public Task DragToAsync(ILocator target, LocatorDragToOptions options = null)
=> _frame.DragAndDropAsync(_selector, ((Locator)target)._selector, ConvertOptions<FrameDragAndDropOptions>(options));
public async Task<IElementHandle> ElementHandleAsync(LocatorElementHandleOptions options = null)
=> await _frame.WaitForSelectorAsync(
_selector,
ConvertOptions<FrameWaitForSelectorOptions>(options)).ConfigureAwait(false);
public Task<IReadOnlyList<IElementHandle>> ElementHandlesAsync()
=> _frame.QuerySelectorAllAsync(_selector);
public Task<T> EvaluateAllAsync<T>(string expression, object arg = null)
=> _frame.EvalOnSelectorAllAsync<T>(_selector, expression, arg);
public Task<JsonElement?> EvaluateAsync(string expression, object arg = null, LocatorEvaluateOptions options = null)
=> EvaluateAsync<JsonElement?>(expression, arg, options);
public Task<T> EvaluateAsync<T>(string expression, object arg = null, LocatorEvaluateOptions options = null)
=> _frame.EvalOnSelectorAsync<T>(_selector, expression, arg, ConvertOptions<FrameEvalOnSelectorOptions>(options));
public async Task<IJSHandle> EvaluateHandleAsync(string expression, object arg = null, LocatorEvaluateHandleOptions options = null)
=> await WithElementAsync(async (e, _) => await e.EvaluateHandleAsync(expression, arg).ConfigureAwait(false), options).ConfigureAwait(false);
public async Task FillAsync(string value, LocatorFillOptions options = null)
=> await _frame.FillAsync(_selector, value, ConvertOptions<FrameFillOptions>(options)).ConfigureAwait(false);
public Task FocusAsync(LocatorFocusOptions options = null)
=> _frame.FocusAsync(_selector, ConvertOptions<FrameFocusOptions>(options));
IFrameLocator ILocator.FrameLocator(string selector) =>
new FrameLocator(_frame, $"{_selector} >> {selector}");
public Task<string> GetAttributeAsync(string name, LocatorGetAttributeOptions options = null)
=> _frame.GetAttributeAsync(_selector, name, ConvertOptions<FrameGetAttributeOptions>(options));
public Task HoverAsync(LocatorHoverOptions options = null)
=> _frame.HoverAsync(_selector, ConvertOptions<FrameHoverOptions>(options));
public Task<string> InnerHTMLAsync(LocatorInnerHTMLOptions options = null)
=> _frame.InnerHTMLAsync(_selector, ConvertOptions<FrameInnerHTMLOptions>(options));
public Task<string> InnerTextAsync(LocatorInnerTextOptions options = null)
=> _frame.InnerTextAsync(_selector, ConvertOptions<FrameInnerTextOptions>(options));
public Task<string> InputValueAsync(LocatorInputValueOptions options = null)
=> _frame.InputValueAsync(_selector, ConvertOptions<FrameInputValueOptions>(options));
public Task<bool> IsCheckedAsync(LocatorIsCheckedOptions options = null)
=> _frame.IsCheckedAsync(_selector, ConvertOptions<FrameIsCheckedOptions>(options));
public Task<bool> IsDisabledAsync(LocatorIsDisabledOptions options = null)
=> _frame.IsDisabledAsync(_selector, ConvertOptions<FrameIsDisabledOptions>(options));
public Task<bool> IsEditableAsync(LocatorIsEditableOptions options = null)
=> _frame.IsEditableAsync(_selector, ConvertOptions<FrameIsEditableOptions>(options));
public Task<bool> IsEnabledAsync(LocatorIsEnabledOptions options = null)
=> _frame.IsEnabledAsync(_selector, ConvertOptions<FrameIsEnabledOptions>(options));
public Task<bool> IsHiddenAsync(LocatorIsHiddenOptions options = null)
=> _frame.IsHiddenAsync(_selector, ConvertOptions<FrameIsHiddenOptions>(options));
public Task<bool> IsVisibleAsync(LocatorIsVisibleOptions options = null)
=> _frame.IsVisibleAsync(_selector, ConvertOptions<FrameIsVisibleOptions>(options));
public ILocator Nth(int index)
=> new Locator(_frame, $"{_selector} >> nth={index}");
public Task PressAsync(string key, LocatorPressOptions options = null)
=> _frame.PressAsync(_selector, key, ConvertOptions<FramePressOptions>(options));
public Task<byte[]> ScreenshotAsync(LocatorScreenshotOptions options = null)
=> WithElementAsync(async (h, o) => await h.ScreenshotAsync(ConvertOptions<ElementHandleScreenshotOptions>(o)).ConfigureAwait(false), options);
public Task ScrollIntoViewIfNeededAsync(LocatorScrollIntoViewIfNeededOptions options = null)
=> WithElementAsync(async (h, o) => await h.ScrollIntoViewIfNeededAsync(ConvertOptions<ElementHandleScrollIntoViewIfNeededOptions>(o)).ConfigureAwait(false), options);
public Task<IReadOnlyList<string>> SelectOptionAsync(string values, LocatorSelectOptionOptions options = null)
=> _frame.SelectOptionAsync(_selector, values, ConvertOptions<FrameSelectOptionOptions>(options));
public Task<IReadOnlyList<string>> SelectOptionAsync(IElementHandle values, LocatorSelectOptionOptions options = null)
=> _frame.SelectOptionAsync(_selector, values, ConvertOptions<FrameSelectOptionOptions>(options));
public Task<IReadOnlyList<string>> SelectOptionAsync(IEnumerable<string> values, LocatorSelectOptionOptions options = null)
=> _frame.SelectOptionAsync(_selector, values, ConvertOptions<FrameSelectOptionOptions>(options));
public Task<IReadOnlyList<string>> SelectOptionAsync(SelectOptionValue values, LocatorSelectOptionOptions options = null)
=> _frame.SelectOptionAsync(_selector, values, ConvertOptions<FrameSelectOptionOptions>(options));
public Task<IReadOnlyList<string>> SelectOptionAsync(IEnumerable<IElementHandle> values, LocatorSelectOptionOptions options = null)
=> _frame.SelectOptionAsync(_selector, values, ConvertOptions<FrameSelectOptionOptions>(options));
public Task<IReadOnlyList<string>> SelectOptionAsync(IEnumerable<SelectOptionValue> values, LocatorSelectOptionOptions options = null)
=> _frame.SelectOptionAsync(_selector, values, ConvertOptions<FrameSelectOptionOptions>(options));
public Task SelectTextAsync(LocatorSelectTextOptions options = null)
=> WithElementAsync((h, o) => h.SelectTextAsync(ConvertOptions<ElementHandleSelectTextOptions>(o)), options);
public Task SetInputFilesAsync(string files, LocatorSetInputFilesOptions options = null)
=> _frame.SetInputFilesAsync(_selector, files, ConvertOptions<FrameSetInputFilesOptions>(options));
public Task SetInputFilesAsync(IEnumerable<string> files, LocatorSetInputFilesOptions options = null)
=> _frame.SetInputFilesAsync(_selector, files, ConvertOptions<FrameSetInputFilesOptions>(options));
public Task SetInputFilesAsync(FilePayload files, LocatorSetInputFilesOptions options = null)
=> _frame.SetInputFilesAsync(_selector, files, ConvertOptions<FrameSetInputFilesOptions>(options));
public Task SetInputFilesAsync(IEnumerable<FilePayload> files, LocatorSetInputFilesOptions options = null)
=> _frame.SetInputFilesAsync(_selector, files, ConvertOptions<FrameSetInputFilesOptions>(options));
public Task TapAsync(LocatorTapOptions options = null)
=> _frame.TapAsync(_selector, ConvertOptions<FrameTapOptions>(options));
public Task<string> TextContentAsync(LocatorTextContentOptions options = null)
=> _frame.TextContentAsync(_selector, ConvertOptions<FrameTextContentOptions>(options));
public Task TypeAsync(string text, LocatorTypeOptions options = null)
=> _frame.TypeAsync(_selector, text, ConvertOptions<FrameTypeOptions>(options));
public Task UncheckAsync(LocatorUncheckOptions options = null)
=> _frame.UncheckAsync(_selector, ConvertOptions<FrameUncheckOptions>(options));
ILocator ILocator.Locator(string selector, LocatorLocatorOptions options)
=> new Locator(_frame, $"{_selector} >> {selector}", options);
public Task WaitForAsync(LocatorWaitForOptions options = null)
=> _frame.LocatorWaitForAsync(_selector, ConvertOptions<LocatorWaitForOptions>(options));
internal Task<FrameExpectResult> ExpectAsync(string expression, FrameExpectOptions options = null)
=> _frame.ExpectAsync(
_selector,
expression,
options);
public override string ToString() => "[email protected]" + _selector;
private T ConvertOptions<T>(object source)
where T : class, new()
{
T target = new();
var targetType = target.GetType();
if (source != null)
{
var sourceType = source.GetType();
foreach (var sourceProperty in sourceType.GetProperties())
{
var targetProperty = targetType.GetProperty(sourceProperty.Name);
if (targetProperty != null)
{
targetProperty.SetValue(target, sourceProperty.GetValue(source));
}
}
}
var strictProperty = targetType.GetProperty("Strict");
if (strictProperty != null && strictProperty.GetValue(target) == null)
{
strictProperty.SetValue(target, true);
}
return target;
}
private async Task<TResult> WithElementAsync<TOptions, TResult>(Func<IElementHandle, TOptions, Task<TResult>> callback, TOptions options)
where TOptions : class
where TResult : class
{
IElementHandle handle = await ElementHandleAsync(ConvertOptions<LocatorElementHandleOptions>(options)).ConfigureAwait(false);
try
{
return await callback(handle, options).ConfigureAwait(false);
}
finally
{
await handle.DisposeAsync().ConfigureAwait(false);
}
}
private async Task WithElementAsync<TOptions>(Func<IElementHandle, TOptions, Task> callback, TOptions options)
where TOptions : class
{
IElementHandle handle = await ElementHandleAsync(ConvertOptions<LocatorElementHandleOptions>(options)).ConfigureAwait(false);
try
{
await callback(handle, options).ConfigureAwait(false);
}
finally
{
await handle.DisposeAsync().ConfigureAwait(false);
}
}
public Task HighlightAsync() => _frame.HighlightAsync(_selector);
}
}
/*
* MIT License
*
* Copyright (c) 2020 DarÃo Kondratiuk
* Copyright (c) 2020 Meir Blachman
* Modifications copyright (c) Microsoft Corporation.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Playwright.Helpers;
using Microsoft.Playwright.Transport;
using Microsoft.Playwright.Transport.Channels;
using Microsoft.Playwright.Transport.Protocol;
namespace Microsoft.Playwright.Core
{
internal class Frame : ChannelOwnerBase, IChannelOwner<Frame>, IFrame
{
internal readonly FrameChannel _channel;
private readonly List<WaitUntilState> _loadStates = new();
internal Frame(IChannelOwner parent, string guid, FrameInitializer initializer) : base(parent, guid)
{
_channel = new(guid, parent.Connection, this);
Url = initializer.Url;
Name = initializer.Name;
ParentFrame = initializer.ParentFrame;
_loadStates = initializer.LoadStates;
_channel.LoadState += (_, e) =>
{
lock (_loadStates)
{
if (e.Add.HasValue)
{
_loadStates.Add(e.Add.Value);
LoadState?.Invoke(this, e.Add.Value);
}
if (e.Remove.HasValue)
{
_loadStates.Remove(e.Remove.Value);
}
}
};
_channel.Navigated += (_, e) =>
{
Url = e.Url;
Name = e.Name;
Navigated?.Invoke(this, e);
if (string.IsNullOrEmpty(e.Error))
{
((Page)Page)?.OnFrameNavigated(this);
}
};
}
/// <summary>
/// Raised when a navigation is received.
/// </summary>
public event EventHandler<FrameNavigatedEventArgs> Navigated;
/// <summary>
/// Raised when a new LoadState was added.
/// </summary>
public event EventHandler<WaitUntilState> LoadState;
ChannelBase IChannelOwner.Channel => _channel;
IChannel<Frame> IChannelOwner<Frame>.Channel => _channel;
public IReadOnlyList<IFrame> ChildFrames => ChildFramesList;
public string Name { get; internal set; }
public string Url { get; internal set; }
IFrame IFrame.ParentFrame => ParentFrame;
public Frame ParentFrame { get; }
public IPage Page { get; internal set; }
public bool IsDetached { get; internal set; }
internal List<Frame> ChildFramesList { get; } = new();
public async Task<IElementHandle> FrameElementAsync()
=> (await _channel.FrameElementAsync().ConfigureAwait(false)).Object;
public IFrameLocator FrameLocator(string selector)
=> new FrameLocator(this, selector);
public Task<string> TitleAsync() => _channel.TitleAsync();
public Task WaitForTimeoutAsync(float timeout)
=> _channel.WaitForTimeoutAsync(timeout);
public Task<IReadOnlyList<string>> SelectOptionAsync(string selector, string values, FrameSelectOptionOptions options = default)
=> SelectOptionAsync(selector, new[] { values }, options);
public Task<IReadOnlyList<string>> SelectOptionAsync(string selector, IEnumerable<string> values, FrameSelectOptionOptions options = default)
=> SelectOptionAsync(selector, values.Select(x => new SelectOptionValue() { Value = x }), options);
public Task<IReadOnlyList<string>> SelectOptionAsync(string selector, IElementHandle values, FrameSelectOptionOptions options = default)
=> SelectOptionAsync(selector, new[] { values }, options);
public async Task<IReadOnlyList<string>> SelectOptionAsync(string selector, IEnumerable<IElementHandle> values, FrameSelectOptionOptions options = default)
=> (await _channel.SelectOptionAsync(
selector,
values.Select(x => x as ElementHandle),
noWaitAfter: options?.NoWaitAfter,
strict: options?.Strict,
force: options?.Force,
timeout: options?.Timeout).ConfigureAwait(false)).ToList().AsReadOnly();
public Task<IReadOnlyList<string>> SelectOptionAsync(string selector, SelectOptionValue values, FrameSelectOptionOptions options = default)
=> SelectOptionAsync(selector, new[] { values }, options);
public async Task<IReadOnlyList<string>> SelectOptionAsync(string selector, IEnumerable<SelectOptionValue> values, FrameSelectOptionOptions options = default)
=> (await _channel.SelectOptionAsync(
selector,
values,
noWaitAfter: options?.NoWaitAfter,
strict: options?.Strict,
force: options?.Force,
timeout: options?.Timeout).ConfigureAwait(false)).ToList().AsReadOnly();
public async Task WaitForLoadStateAsync(LoadState? state = default, FrameWaitForLoadStateOptions options = default)
{
Task<WaitUntilState> task;
Waiter waiter = null;
WaitUntilState loadState = Microsoft.Playwright.WaitUntilState.Load;
switch (state)
{
case Microsoft.Playwright.LoadState.Load:
loadState = Microsoft.Playwright.WaitUntilState.Load;
break;
case Microsoft.Playwright.LoadState.DOMContentLoaded:
loadState = Microsoft.Playwright.WaitUntilState.DOMContentLoaded;
break;
case Microsoft.Playwright.LoadState.NetworkIdle:
loadState = Microsoft.Playwright.WaitUntilState.NetworkIdle;
break;
}
try
{
lock (_loadStates)
{
if (_loadStates.Contains(loadState))
{
return;
}
waiter = SetupNavigationWaiter("frame.WaitForLoadStateAsync", options?.Timeout);
task = waiter.WaitForEventAsync<WaitUntilState>(this, "LoadState", s =>
{
waiter.Log($" \"{s}\" event fired");
return s == loadState;
});
}
await task.ConfigureAwait(false);
}
finally
{
waiter?.Dispose();
}
}
public async Task<IResponse> WaitForNavigationAsync(FrameWaitForNavigationOptions options = default)
{
WaitUntilState waitUntil2 = options?.WaitUntil ?? WaitUntilState.Load;
using var waiter = SetupNavigationWaiter("frame.WaitForNavigationAsync", options?.Timeout);
string toUrl = !string.IsNullOrEmpty(options?.UrlString) ? $" to \"{options?.UrlString}\"" : string.Empty;
waiter.Log($"waiting for navigation{toUrl} until \"{waitUntil2}\"");
var navigatedEventTask = waiter.WaitForEventAsync<FrameNavigatedEventArgs>(
this,
"Navigated",
e =>
{
// Any failed navigation results in a rejection.
if (e.Error != null)
{
return true;
}
waiter.Log($" navigated to \"{e.Url}\"");
return UrlMatches(e.Url, options?.UrlString, options?.UrlRegex, options?.UrlFunc);
});
var navigatedEvent = await navigatedEventTask.ConfigureAwait(false);
if (navigatedEvent.Error != null)
{
var ex = new PlaywrightException(navigatedEvent.Error);
await waiter.WaitForPromiseAsync(Task.FromException<object>(ex)).ConfigureAwait(false);
}
if (!_loadStates.Select(s => s.ToValueString()).Contains(waitUntil2.ToValueString()))
{
await waiter.WaitForEventAsync<WaitUntilState>(
this,
"LoadState",
e =>
{
waiter.Log($" \"{e}\" event fired");
return e.ToValueString() == waitUntil2.ToValueString();
}).ConfigureAwait(false);
}
var request = navigatedEvent.NewDocument?.Request?.Object;
var response = request != null
? await waiter.WaitForPromiseAsync(request.FinalRequest.ResponseAsync()).ConfigureAwait(false)
: null;
return response;
}
public async Task<IResponse> RunAndWaitForNavigationAsync(Func<Task> action, FrameRunAndWaitForNavigationOptions options = default)
{
var result = WaitForNavigationAsync(new()
{
UrlString = options?.UrlString,
UrlRegex = options?.UrlRegex,
UrlFunc = options?.UrlFunc,
WaitUntil = options?.WaitUntil,
Timeout = options?.Timeout,
});
if (action != null)
{
await WrapApiBoundaryAsync(() => Task.WhenAll(result, action())).ConfigureAwait(false);
}
return await result.ConfigureAwait(false);
}
public Task TapAsync(string selector, FrameTapOptions options = default)
=> _channel.TapAsync(
selector,
modifiers: options?.Modifiers,
position: options?.Position,
timeout: options?.Timeout,
force: options?.Force,
noWaitAfter: options?.NoWaitAfter,
trial: options?.Trial,
strict: options?.Strict);
internal Task<int> QueryCountAsync(string selector)
=> _channel.QueryCountAsync(selector);
public Task<string> ContentAsync() => _channel.ContentAsync();
public Task FocusAsync(string selector, FrameFocusOptions options = default)
=> _channel.FocusAsync(selector, options?.Timeout, options?.Strict);
public Task TypeAsync(string selector, string text, FrameTypeOptions options = default)
=> _channel.TypeAsync(
selector,
text,
delay: options?.Delay,
timeout: options?.Timeout,
noWaitAfter: options?.NoWaitAfter,
strict: options?.Strict);
public Task<string> GetAttributeAsync(string selector, string name, FrameGetAttributeOptions options = default)
=> _channel.GetAttributeAsync(selector, name, options?.Timeout, options?.Strict);
public Task<string> InnerHTMLAsync(string selector, FrameInnerHTMLOptions options = default)
=> _channel.InnerHTMLAsync(selector, options?.Timeout, options?.Strict);
public Task<string> InnerTextAsync(string selector, FrameInnerTextOptions options = default)
=> _channel.InnerTextAsync(selector, options?.Timeout, options?.Strict);
public Task<string> TextContentAsync(string selector, FrameTextContentOptions options = default)
=> _channel.TextContentAsync(selector, options?.Timeout, options?.Strict);
public Task HoverAsync(string selector, FrameHoverOptions options = default)
=> _channel.HoverAsync(
selector,
position: options?.Position,
modifiers: options?.Modifiers,
force: options?.Force,
timeout: options?.Timeout,
trial: options?.Trial,
strict: options?.Strict);
public Task PressAsync(string selector, string key, FramePressOptions options = default)
=> _channel.PressAsync(
selector,
key,
delay: options?.Delay,
timeout: options?.Timeout,
noWaitAfter: options?.NoWaitAfter,
strict: options?.Strict);
public Task DispatchEventAsync(string selector, string type, object eventInit = default, FrameDispatchEventOptions options = default)
=> _channel.DispatchEventAsync(
selector,
type,
ScriptsHelper.SerializedArgument(eventInit),
options?.Timeout,
options?.Strict);
public Task FillAsync(string selector, string value, FrameFillOptions options = default)
=> _channel.FillAsync(selector, value, force: options?.Force, timeout: options?.Timeout, noWaitAfter: options?.NoWaitAfter, options?.Strict);
public async Task<IElementHandle> AddScriptTagAsync(FrameAddScriptTagOptions options = default)
{
var content = options?.Content;
if (!string.IsNullOrEmpty(options?.Path))
{
content = File.ReadAllText(options.Path);
content += "//# sourceURL=" + options.Path.Replace("\n", string.Empty);
}
return (await _channel.AddScriptTagAsync(options?.Url, options?.Path, content, options?.Type).ConfigureAwait(false)).Object;
}
public async Task<IElementHandle> AddStyleTagAsync(FrameAddStyleTagOptions options = default)
{
var content = options?.Content;
if (!string.IsNullOrEmpty(options?.Path))
{
content = File.ReadAllText(options.Path);
content += "//# sourceURL=" + options.Path.Replace("\n", string.Empty);
}
return (await _channel.AddStyleTagAsync(options?.Url, options?.Path, content).ConfigureAwait(false)).Object;
}
public Task SetInputFilesAsync(string selector, string files, FrameSetInputFilesOptions options = default)
=> SetInputFilesAsync(selector, new[] { files }, options);
public async Task SetInputFilesAsync(string selector, IEnumerable<string> files, FrameSetInputFilesOptions options = default)
{
var converted = await SetInputFilesHelpers.ConvertInputFilesAsync(files, (BrowserContext)Page.Context).ConfigureAwait(false);
if (converted.Files != null)
{
await _channel.SetInputFilesAsync(selector, converted.Files, options?.NoWaitAfter, options?.Timeout, options?.Strict).ConfigureAwait(false);
}
else
{
await _channel.SetInputFilePathsAsync(selector, converted?.LocalPaths, converted?.Streams, options?.NoWaitAfter, options?.Timeout, options?.Strict).ConfigureAwait(false);
}
}
public Task SetInputFilesAsync(string selector, FilePayload files, FrameSetInputFilesOptions options = default)
=> SetInputFilesAsync(selector, new[] { files }, options);
public async Task SetInputFilesAsync(string selector, IEnumerable<FilePayload> files, FrameSetInputFilesOptions options = default)
{
var converted = SetInputFilesHelpers.ConvertInputFiles(files);
await _channel.SetInputFilesAsync(selector, converted.Files, noWaitAfter: options?.NoWaitAfter, timeout: options?.Timeout, options?.Strict).ConfigureAwait(false);
}
public Task ClickAsync(string selector, FrameClickOptions options = default)
=> _channel.ClickAsync(
selector,
delay: options?.Delay,
button: options?.Button,
clickCount: options?.ClickCount,
modifiers: options?.Modifiers,
position: options?.Position,
timeout: options?.Timeout,
force: options?.Force,
noWaitAfter: options?.NoWaitAfter,
trial: options?.Trial,
strict: options?.Strict);
public Task DblClickAsync(string selector, FrameDblClickOptions options = default)
=> _channel.DblClickAsync(
selector,
delay: options?.Delay,
button: options?.Button,
position: options?.Position,
modifiers: options?.Modifiers,
timeout: options?.Timeout,
force: options?.Force,
noWaitAfter: options?.NoWaitAfter,
trial: options?.Trial,
strict: options?.Strict);
public Task CheckAsync(string selector, FrameCheckOptions options = default)
=> _channel.CheckAsync(
selector,
position: options?.Position,
timeout: options?.Timeout,
force: options?.Force,
noWaitAfter: options?.NoWaitAfter,
trial: options?.Trial,
strict: options?.Strict);
public Task UncheckAsync(string selector, FrameUncheckOptions options = default)
=> _channel.UncheckAsync(
selector,
position: options?.Position,
timeout: options?.Timeout,
force: options?.Force,
noWaitAfter: options?.NoWaitAfter,
trial: options?.Trial,
strict: options?.Strict);
public Task SetCheckedAsync(string selector, bool checkedState, FrameSetCheckedOptions options = null)
=> checkedState ?
_channel.CheckAsync(
selector,
position: options?.Position,
timeout: options?.Timeout,
force: options?.Force,
noWaitAfter: options?.NoWaitAfter,
trial: options?.Trial,
strict: options?.Strict)
: _channel.UncheckAsync(
selector,
position: options?.Position,
timeout: options?.Timeout,
force: options?.Force,
noWaitAfter: options?.NoWaitAfter,
trial: options?.Trial,
strict: options?.Strict);
public Task SetContentAsync(string html, FrameSetContentOptions options = default)
=> _channel.SetContentAsync(html, timeout: options?.Timeout, waitUntil: options?.WaitUntil);
public Task<string> InputValueAsync(string selector, FrameInputValueOptions options = null)
=> _channel.InputValueAsync(selector, options?.Timeout, options?.Strict);
public async Task<IElementHandle> QuerySelectorAsync(string selector)
=> (await _channel.QuerySelectorAsync(selector).ConfigureAwait(false))?.Object;
public async Task<IReadOnlyList<IElementHandle>> QuerySelectorAllAsync(string selector)
=> (await _channel.QuerySelectorAllAsync(selector).ConfigureAwait(false)).Select(c => ((ElementHandleChannel)c).Object).ToList().AsReadOnly();
public async Task<IJSHandle> WaitForFunctionAsync(string expression, object arg = default, FrameWaitForFunctionOptions options = default)
=> (await _channel.WaitForFunctionAsync(
expression: expression,
arg: ScriptsHelper.SerializedArgument(arg),
timeout: options?.Timeout,
polling: options?.PollingInterval).ConfigureAwait(false)).Object;
public async Task<IElementHandle> WaitForSelectorAsync(string selector, FrameWaitForSelectorOptions options = default)
=> (await _channel.WaitForSelectorAsync(
selector: selector,
state: options?.State,
timeout: options?.Timeout,
strict: options?.Strict,
omitReturnValue: false).ConfigureAwait(false))?.Object;
public async Task<IElementHandle> LocatorWaitForAsync(string selector, LocatorWaitForOptions options = default)
=> (await _channel.WaitForSelectorAsync(
selector: selector,
state: options?.State,
timeout: options?.Timeout,
strict: true,
omitReturnValue: true).ConfigureAwait(false))?.Object;
public async Task<IJSHandle> EvaluateHandleAsync(string script, object args = null)
=> (await _channel.EvaluateExpressionHandleAsync(
script,
arg: ScriptsHelper.SerializedArgument(args)).ConfigureAwait(false))?.Object;
public async Task<JsonElement?> EvaluateAsync(string script, object arg = null)
=> ScriptsHelper.ParseEvaluateResult<JsonElement?>(await _channel.EvaluateExpressionAsync(
script,
arg: ScriptsHelper.SerializedArgument(arg)).ConfigureAwait(false));
public async Task<T> EvaluateAsync<T>(string script, object arg = null)
=> ScriptsHelper.ParseEvaluateResult<T>(await _channel.EvaluateExpressionAsync(
script,
arg: ScriptsHelper.SerializedArgument(arg)).ConfigureAwait(false));
public async Task<JsonElement?> EvalOnSelectorAsync(string selector, string script, object arg = null)
=> ScriptsHelper.ParseEvaluateResult<JsonElement?>(await _channel.EvalOnSelectorAsync(
selector: selector,
script,
arg: ScriptsHelper.SerializedArgument(arg),
strict: null).ConfigureAwait(false));
public async Task<T> EvalOnSelectorAsync<T>(string selector, string script, object arg = null)
=> ScriptsHelper.ParseEvaluateResult<T>(await _channel.EvalOnSelectorAsync(
selector: selector,
script,
arg: ScriptsHelper.SerializedArgument(arg),
strict: null).ConfigureAwait(false));
public async Task<T> EvalOnSelectorAsync<T>(string selector, string expression, object arg = null, FrameEvalOnSelectorOptions options = null)
=> ScriptsHelper.ParseEvaluateResult<T>(await _channel.EvalOnSelectorAsync(
selector: selector,
expression,
arg: ScriptsHelper.SerializedArgument(arg),
strict: options?.Strict).ConfigureAwait(false));
public async Task<JsonElement?> EvalOnSelectorAllAsync(string selector, string script, object arg = null)
=> ScriptsHelper.ParseEvaluateResult<JsonElement?>(await _channel.EvalOnSelectorAllAsync(
selector: selector,
script,
arg: ScriptsHelper.SerializedArgument(arg)).ConfigureAwait(false));
public async Task<T> EvalOnSelectorAllAsync<T>(string selector, string script, object arg = null)
=> ScriptsHelper.ParseEvaluateResult<T>(await _channel.EvalOnSelectorAllAsync(
selector: selector,
script,
arg: ScriptsHelper.SerializedArgument(arg)).ConfigureAwait(false));
public ILocator Locator(string selector, FrameLocatorOptions options = null) => new Locator(this, selector, new() { HasTextRegex = options?.HasTextRegex, HasTextString = options?.HasTextString, Has = options?.Has });
public async Task<IElementHandle> QuerySelectorAsync(string selector, FrameQuerySelectorOptions options = null)
=> (await _channel.QuerySelectorAsync(selector, options?.Strict).ConfigureAwait(false))?.Object;
public async Task<IResponse> GotoAsync(string url, FrameGotoOptions options = default)
=> (await _channel.GotoAsync(
url,
timeout: options?.Timeout,
waitUntil: options?.WaitUntil,
referer: options?.Referer).ConfigureAwait(false))?.Object;
public Task<bool> IsCheckedAsync(string selector, FrameIsCheckedOptions options = default)
=> _channel.IsCheckedAsync(selector, timeout: options?.Timeout, options?.Strict);
public Task<bool> IsDisabledAsync(string selector, FrameIsDisabledOptions options = default)
=> _channel.IsDisabledAsync(selector, timeout: options?.Timeout, options?.Strict);
public Task<bool> IsEditableAsync(string selector, FrameIsEditableOptions options = default)
=> _channel.IsEditableAsync(selector, timeout: options?.Timeout, options?.Strict);
public Task<bool> IsEnabledAsync(string selector, FrameIsEnabledOptions options = default)
=> _channel.IsEnabledAsync(selector, timeout: options?.Timeout, options?.Strict);
#pragma warning disable CS0612 // Type or member is obsolete
public Task<bool> IsHiddenAsync(string selector, FrameIsHiddenOptions options = default)
=> _channel.IsHiddenAsync(selector, timeout: options?.Timeout, options?.Strict);
public Task<bool> IsVisibleAsync(string selector, FrameIsVisibleOptions options = default)
=> _channel.IsVisibleAsync(selector, timeout: options?.Timeout, options?.Strict);
#pragma warning restore CS0612 // Type or member is obsolete
public Task WaitForURLAsync(string url, FrameWaitForURLOptions options = default)
=> WaitForURLAsync(url, null, null, options);
public Task WaitForURLAsync(Regex url, FrameWaitForURLOptions options = default)
=> WaitForURLAsync(null, url, null, options);
public Task WaitForURLAsync(Func<string, bool> url, FrameWaitForURLOptions options = default)
=> WaitForURLAsync(null, null, url, options);
public Task DragAndDropAsync(string source, string target, FrameDragAndDropOptions options = null)
=> _channel.DragAndDropAsync(source, target, options?.Force, options?.NoWaitAfter, options?.Timeout, options?.Trial, options?.Strict);
internal Task<FrameExpectResult> ExpectAsync(string selector, string expression, FrameExpectOptions options = null) =>
_channel.ExpectAsync(selector, expression, expressionArg: options?.ExpressionArg, expectedText: options?.ExpectedText, expectedNumber: options?.ExpectedNumber, expectedValue: options?.ExpectedValue, useInnerText: options?.UseInnerText, isNot: options?.IsNot, timeout: options?.Timeout);
private Task WaitForURLAsync(string urlString, Regex urlRegex, Func<string, bool> urlFunc, FrameWaitForURLOptions options = default)
{
if (UrlMatches(Url, urlString, urlRegex, urlFunc))
{
return WaitForLoadStateAsync(ToLoadState(options?.WaitUntil), new() { Timeout = options?.Timeout });
}
return WaitForNavigationAsync(
new()
{
UrlString = urlString,
UrlRegex = urlRegex,
UrlFunc = urlFunc,
Timeout = options?.Timeout,
WaitUntil = options?.WaitUntil,
});
}
private LoadState? ToLoadState(WaitUntilState? waitUntilState)
{
if (waitUntilState == null)
{
return null;
}
return waitUntilState switch
{
WaitUntilState.Load => Microsoft.Playwright.LoadState.Load,
WaitUntilState.DOMContentLoaded => Microsoft.Playwright.LoadState.DOMContentLoaded,
WaitUntilState.NetworkIdle => Microsoft.Playwright.LoadState.NetworkIdle,
_ => null,
};
}
private Waiter SetupNavigationWaiter(string @event, float? timeout)
{
var waiter = new Waiter(this.Page as Page, @event);
if (this.Page.IsClosed)
{
waiter.RejectImmediately(new PlaywrightException("Navigation failed because page was closed!"));
}
waiter.RejectOnEvent<IPage>(Page, PageEvent.Close.Name, new("Navigation failed because page was closed!"));
waiter.RejectOnEvent<IPage>(Page, PageEvent.Crash.Name, new("Navigation failed because page was crashed!"));
waiter.RejectOnEvent<IFrame>(
Page,
"FrameDetached",
new("Navigating frame was detached!"),
e => e == this);
timeout ??= (Page as Page)?.DefaultNavigationTimeout ?? PlaywrightImpl.DefaultTimeout;
waiter.RejectOnTimeout(Convert.ToInt32(timeout), $"Timeout {timeout}ms exceeded.");
return waiter;
}
private bool UrlMatches(string url, string matchUrl, Regex regex, Func<string, bool> match)
{
matchUrl = (Page.Context as BrowserContext)?.CombineUrlWithBase(matchUrl);
if (matchUrl == null && regex == null && match == null)
{
return true;
}
if (!string.IsNullOrEmpty(matchUrl))
{
regex = new(matchUrl.GlobToRegex());
}
if (matchUrl != null && url == matchUrl)
{
return true;
}
if (regex != null)
{
return regex.IsMatch(url);
}
return match(url);
}
internal Task HighlightAsync(string selector)
=> _channel.HighlightAsync(selector);
}
}
/*
* MIT License
*
* Copyright (c) Microsoft Corporation.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Playwright.Helpers;
using Microsoft.Playwright.NUnit;
using NUnit.Framework;
namespace Microsoft.Playwright.Tests
{
///<playwright-file>browsertype-connect.spec.ts</playwright-file>
public class BrowserTypeConnectTests : PlaywrightTestEx
{
private RemoteServer _remoteServer;
[SetUp]
public void SetUpRemoteServer()
{
_remoteServer = new(BrowserType.Name);
}
[TearDown]
public void TearDownRemoteServer()
{
_remoteServer.Close();
}
[PlaywrightTest("browsertype-connect.spec.ts", "should be able to reconnect to a browser")]
public async Task ShouldBeAbleToReconnectToABrowser()
{
{
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
var browserContext = await browser.NewContextAsync();
Assert.AreEqual(browserContext.Pages.Count, 0);
var page = await browserContext.NewPageAsync();
Assert.AreEqual(await page.EvaluateAsync<int>("11 * 11"), 121);
await page.GotoAsync(Server.EmptyPage);
await browser.CloseAsync();
}
{
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
var browserContext = await browser.NewContextAsync();
var page = await browserContext.NewPageAsync();
await page.GotoAsync(Server.EmptyPage);
await browser.CloseAsync();
}
}
[PlaywrightTest("browsertype-connect.spec.ts", "should send default User-Agent and X-Playwright-Browser headers with connect request")]
public async Task ShouldSendDefaultUserAgentAndPlaywrightBrowserHeadersWithConnectRequest()
{
var connectionRequest = Server.WaitForWebSocketConnectionRequest();
BrowserType.ConnectAsync($"ws://localhost:{Server.Port}/ws", new()
{
Headers = new Dictionary<string, string>()
{
["hello-foo"] = "i-am-bar",
}
}).IgnoreException();
var request = await connectionRequest;
StringAssert.Contains("Playwright", request.Headers["User-Agent"]);
Assert.AreEqual(request.Headers["hello-foo"], "i-am-bar");
Assert.AreEqual(request.Headers["x-playwright-browser"], BrowserType.Name);
}
[PlaywrightTest("browsertype-connect.spec.ts", "should be able to connect two browsers at the same time")]
public async Task ShouldBeAbleToConnectTwoBrowsersAtTheSameTime()
{
var browser1 = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
Assert.AreEqual(browser1.Contexts.Count, 0);
await browser1.NewContextAsync();
Assert.AreEqual(browser1.Contexts.Count, 1);
var browser2 = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
Assert.AreEqual(browser2.Contexts.Count, 0);
await browser2.NewContextAsync();
Assert.AreEqual(browser2.Contexts.Count, 1);
Assert.AreEqual(browser1.Contexts.Count, 1);
await browser1.CloseAsync();
Assert.AreEqual(browser2.Contexts.Count, 1);
var page2 = await browser2.NewPageAsync();
Assert.AreEqual(await page2.EvaluateAsync<int>("7 * 6"), 42); // original browser should still work
await browser2.CloseAsync();
}
[PlaywrightTest("browsertype-connect.spec.ts", "should timeout in connect while connecting")]
[Skip(SkipAttribute.Targets.Windows)]
public async Task ShouldTimeoutInConnectWhileConnecting()
{
var exception = await PlaywrightAssert.ThrowsAsync<TimeoutException>(async () => await BrowserType.ConnectAsync($"ws://localhost:{Server.Port}/ws", new BrowserTypeConnectOptions { Timeout = 100 }));
StringAssert.Contains("BrowserType.ConnectAsync: Timeout 100ms exceeded", exception.Message);
}
[PlaywrightTest("browsertype-connect.spec.ts", "should support slowmo option")]
public async Task ShouldSupportSlowMo()
{
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint, new BrowserTypeConnectOptions { SlowMo = 200 });
var start = DateTime.Now;
var context = await browser.NewContextAsync();
await browser.CloseAsync();
Assert.Greater((DateTime.Now - start).TotalMilliseconds, 199);
}
[PlaywrightTest("browsertype-connect.spec.ts", "disconnected event should be emitted when browser is closed or server is closed")]
public async Task DisconnectedEventShouldBeEmittedWhenBrowserIsClosedOrServerIsClosed()
{
var browser1 = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
await browser1.NewPageAsync();
var browser2 = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
await browser2.NewPageAsync();
int disconnected1 = 0;
int disconnected2 = 0;
browser1.Disconnected += (_, e) => disconnected1++;
browser2.Disconnected += (_, e) => disconnected2++;
var tsc1 = new TaskCompletionSource<object>();
browser1.Disconnected += (_, e) => tsc1.SetResult(null);
await browser1.CloseAsync();
await tsc1.Task;
Assert.AreEqual(disconnected1, 1);
Assert.AreEqual(disconnected2, 0);
var tsc2 = new TaskCompletionSource<object>();
browser2.Disconnected += (_, e) => tsc2.SetResult(null);
await browser2.CloseAsync();
await tsc2.Task;
Assert.AreEqual(disconnected1, 1);
Assert.AreEqual(disconnected2, 1);
}
[PlaywrightTest("browsertype-connect.spec.ts", "disconnected event should have browser as argument")]
public async Task DisconnectedEventShouldHaveBrowserAsArguments()
{
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
IBrowser disconneced = null;
var tsc = new TaskCompletionSource<object>();
browser.Disconnected += (_, browser) =>
{
disconneced = browser;
tsc.SetResult(null);
};
await browser.CloseAsync();
await tsc.Task;
Assert.AreEqual(browser, disconneced);
}
[PlaywrightTest("browsertype-connect.spec.ts", "should set the browser connected state")]
public async Task ShouldSetTheBrowserConnectedState()
{
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
Assert.AreEqual(browser.IsConnected, true);
var tsc = new TaskCompletionSource<bool>();
browser.Disconnected += (_, e) => tsc.SetResult(false);
_remoteServer.Close();
await tsc.Task;
Assert.AreEqual(browser.IsConnected, false);
}
[PlaywrightTest("browsertype-connect.spec.ts", "should throw when used after isConnected returns false")]
public async Task ShouldThrowWhenUsedAfterIsConnectedReturnsFalse()
{
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
var page = await browser.NewPageAsync();
var tsc = new TaskCompletionSource<bool>();
browser.Disconnected += (_, e) => tsc.SetResult(false);
_remoteServer.Close();
await tsc.Task;
Assert.AreEqual(browser.IsConnected, false);
var exception = await PlaywrightAssert.ThrowsAsync<PlaywrightException>(async () => await page.EvaluateAsync("1 + 1"));
StringAssert.Contains("has been closed", exception.Message);
}
[PlaywrightTest("browsertype-connect.spec.ts", "should throw when calling waitForNavigation after disconnect")]
public async Task ShouldThrowWhenWhenCallingWaitForNavigationAfterDisconnect()
{
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
var page = await browser.NewPageAsync();
var tsc = new TaskCompletionSource<bool>();
browser.Disconnected += (_, e) => tsc.SetResult(false);
_remoteServer.Close();
await tsc.Task;
Assert.AreEqual(browser.IsConnected, false);
var exception = await PlaywrightAssert.ThrowsAsync<PlaywrightException>(async () => await page.WaitForNavigationAsync());
StringAssert.Contains("Navigation failed because page was closed", exception.Message);
}
[PlaywrightTest("browsertype-connect.spec.ts", "should reject navigation when browser closes")]
public async Task ShouldRejectNavigationWhenBrowserCloses()
{
Server.SetRoute("/one-style.css", context =>
{
context.Response.Redirect("/one-style.css");
return Task.CompletedTask;
});
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
var page = await browser.NewPageAsync();
var PageGoto = page.GotoAsync(Server.Prefix + "/one-style.html", new PageGotoOptions { Timeout = 60000 });
await Server.WaitForRequest("/one-style.css");
await browser.CloseAsync();
Assert.AreEqual(browser.IsConnected, false);
var exception = await PlaywrightAssert.ThrowsAsync<PlaywrightException>(async () => await PageGoto);
StringAssert.Contains("has been closed", exception.Message);
}
[PlaywrightTest("browsertype-connect.spec.ts", "should reject waitForSelector when browser closes")]
public async Task ShouldRejectWaitForSelectorWhenBrowserCloses()
{
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
var page = await browser.NewPageAsync();
var watchdog = page.WaitForSelectorAsync("div");
await browser.CloseAsync();
var exception = await PlaywrightAssert.ThrowsAsync<PlaywrightException>(async () => await watchdog);
Assert.That(exception.Message, Contains.Substring("has been closed"));
}
[PlaywrightTest("browsertype-connect.spec.ts", "should emit close events on pages and contexts")]
public async Task ShouldEmitCloseEventsOnPagesAndContexts()
{
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
var context = await browser.NewContextAsync();
var tsc = new TaskCompletionSource<object>();
context.Close += (_, e) => tsc.SetResult(null);
var page = await context.NewPageAsync();
bool pageClosed = false;
page.Close += (_, e) => pageClosed = true;
_remoteServer.Close();
await tsc.Task;
Assert.AreEqual(pageClosed, true);
}
[PlaywrightTest("browsertype-connect.spec.ts", "should terminate network waiters")]
public async Task ShouldTerminateNetworkWaiters()
{
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
var page = await browser.NewPageAsync();
var requestWatchdog = page.WaitForRequestAsync(Server.EmptyPage);
var responseWatchog = page.WaitForResponseAsync(Server.EmptyPage);
_remoteServer.Close();
async Task CheckTaskHasException(Task task)
{
var exception = await PlaywrightAssert.ThrowsAsync<PlaywrightException>(async () => await task);
StringAssert.Contains("Page closed", exception.Message);
StringAssert.DoesNotContain("Timeout", exception.Message);
}
await CheckTaskHasException(requestWatchdog);
await CheckTaskHasException(responseWatchog);
}
[PlaywrightTest("browsertype-connect.spec.ts", "should not throw on close after disconnect")]
public async Task ShouldNotThrowOnCloseAfterDisconnect()
{
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
var page = await browser.NewPageAsync();
var tcs = new TaskCompletionSource<bool>();
browser.Disconnected += (_, e) => tcs.SetResult(true);
_remoteServer.Close();
await tcs.Task;
await browser.CloseAsync();
}
[PlaywrightTest("browsertype-connect.spec.ts", "should not throw on context.close after disconnect")]
public async Task ShouldNotThrowOnContextCloseAfterDisconnect()
{
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
var context = await browser.NewContextAsync();
await context.NewPageAsync();
var tcs = new TaskCompletionSource<bool>();
browser.Disconnected += (_, e) => tcs.SetResult(true);
_remoteServer.Close();
await tcs.Task;
await context.CloseAsync();
}
[PlaywrightTest("browsertype-connect.spec.ts", "should not throw on page.close after disconnect")]
public async Task ShouldNotThrowOnPageCloseAfterDisconnect()
{
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
var context = await browser.NewContextAsync();
var page = await context.NewPageAsync();
var tcs = new TaskCompletionSource<bool>();
browser.Disconnected += (_, e) => tcs.SetResult(true);
_remoteServer.Close();
await tcs.Task;
await page.CloseAsync();
}
[PlaywrightTest("browsertype-connect.spec.ts", "should saveAs videos from remote browser")]
public async Task ShouldSaveAsVideosFromRemoteBrowser()
{
using var tempDirectory = new TempDirectory();
var videoPath = tempDirectory.Path;
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
var context = await browser.NewContextAsync(new()
{
RecordVideoDir = videoPath,
RecordVideoSize = new() { Height = 320, Width = 240 }
});
var page = await context.NewPageAsync();
await page.EvaluateAsync("() => document.body.style.backgroundColor = 'red'");
await Task.Delay(1000);
await context.CloseAsync();
var videoSavePath = tempDirectory.Path + "my-video.webm";
await page.Video.SaveAsAsync(videoSavePath);
Assert.That(videoSavePath, Does.Exist);
var exception = await PlaywrightAssert.ThrowsAsync<PlaywrightException>(async () => await page.Video.PathAsync());
StringAssert.Contains("Path is not available when connecting remotely. Use SaveAsAsync() to save a local copy", exception.Message);
}
[PlaywrightTest("browsertype-connect.spec.ts", "should save download")]
public async Task ShouldSaveDownload()
{
Server.SetRoute("/download", context =>
{
context.Response.Headers["Content-Type"] = "application/octet-stream";
context.Response.Headers["Content-Disposition"] = "attachment";
return context.Response.WriteAsync("Hello world");
});
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
var page = await browser.NewPageAsync(new() { AcceptDownloads = true });
await page.SetContentAsync($"<a href=\"{Server.Prefix}/download\">download</a>");
var downloadTask = page.WaitForDownloadAsync();
await TaskUtils.WhenAll(
downloadTask,
page.ClickAsync("a"));
using var tmpDir = new TempDirectory();
string userPath = Path.Combine(tmpDir.Path, "these", "are", "directories", "download.txt");
var download = downloadTask.Result;
await download.SaveAsAsync(userPath);
Assert.True(new FileInfo(userPath).Exists);
Assert.AreEqual("Hello world", File.ReadAllText(userPath));
var exception = await PlaywrightAssert.ThrowsAsync<PlaywrightException>(() => download.PathAsync());
Assert.AreEqual("Path is not available when connecting remotely. Use SaveAsAsync() to save a local copy.", exception.Message);
await browser.CloseAsync();
}
[PlaywrightTest("browsertype-connect.spec.ts", "should error when saving download after deletion")]
public async Task ShouldErrorWhenSavingDownloadAfterDeletion()
{
Server.SetRoute("/download", context =>
{
context.Response.Headers["Content-Type"] = "application/octet-stream";
context.Response.Headers["Content-Disposition"] = "attachment";
return context.Response.WriteAsync("Hello world");
});
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
var page = await browser.NewPageAsync(new() { AcceptDownloads = true });
await page.SetContentAsync($"<a href=\"{Server.Prefix}/download\">download</a>");
var downloadTask = page.WaitForDownloadAsync();
await TaskUtils.WhenAll(
downloadTask,
page.ClickAsync("a"));
using var tmpDir = new TempDirectory();
string userPath = Path.Combine(tmpDir.Path, "download.txt");
var download = downloadTask.Result;
await download.DeleteAsync();
var exception = await PlaywrightAssert.ThrowsAsync<PlaywrightException>(() => download.SaveAsAsync(userPath));
StringAssert.Contains("Target page, context or browser has been closed", exception.Message);
await browser.CloseAsync();
}
[PlaywrightTest("browsertype-connect.spec.ts", "should save har")]
public async Task ShouldSaveHar()
{
using var tempDirectory = new TempDirectory();
var harPath = tempDirectory.Path + "/test.har";
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
var context = await browser.NewContextAsync(new()
{
RecordHarPath = harPath
});
var page = await context.NewPageAsync();
await page.GotoAsync(Server.EmptyPage);
await context.CloseAsync();
await browser.CloseAsync();
Assert.That(harPath, Does.Exist);
var logString = System.IO.File.ReadAllText(harPath);
StringAssert.Contains(Server.EmptyPage, logString);
}
[PlaywrightTest("browsertype-connect.spec.ts", "should record trace with sources")]
public async Task ShouldRecordContextTraces()
{
using var tempDirectory = new TempDirectory();
var tracePath = tempDirectory.Path + "/trace.zip";
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
var context = await browser.NewContextAsync();
var page = await context.NewPageAsync();
await context.Tracing.StartAsync(new() { Sources = true });
await page.GotoAsync(Server.EmptyPage);
await page.SetContentAsync("<button>Click</button>");
await page.ClickAsync("button");
await context.Tracing.StopAsync(new TracingStopOptions { Path = tracePath });
await browser.CloseAsync();
Assert.That(tracePath, Does.Exist);
ZipFile.ExtractToDirectory(tracePath, tempDirectory.Path);
Assert.That(tempDirectory.Path + "/trace.trace", Does.Exist);
Assert.That(tempDirectory.Path + "/trace.network", Does.Exist);
Assert.AreEqual(1, Directory.GetFiles(Path.Join(tempDirectory.Path, "resources"), "*.txt").Length);
}
[PlaywrightTest("browsertype-connect.spec.ts", "should upload large file")]
[Skip(SkipAttribute.Targets.Firefox, SkipAttribute.Targets.Webkit)]
public async Task ShouldUploadLargeFile()
{
var browser = await BrowserType.ConnectAsync(_remoteServer.WSEndpoint);
var context = await browser.NewContextAsync();
var page = await context.NewPageAsync();
await page.GotoAsync(Server.Prefix + "/input/fileupload.html");
using var tmpDir = new TempDirectory();
var filePath = Path.Combine(tmpDir.Path, "200MB");
using (var stream = File.OpenWrite(filePath))
{
var str = new string('a', 4 * 1024);
for (var i = 0; i < 50 * 1024; i++)
{
await stream.WriteAsync(Encoding.UTF8.GetBytes(str));
}
}
var input = page.Locator("input[type=file]");
var events = await input.EvaluateHandleAsync(@"e => {
const events = [];
e.addEventListener('input', () => events.push('input'));
e.addEventListener('change', () => events.push('change'));
return events;
}");
await input.SetInputFilesAsync(filePath);
Assert.AreEqual(await input.EvaluateAsync<string>("e => e.files[0].name"), "200MB");
Assert.AreEqual(await events.EvaluateAsync<string[]>("e => e"), new[] { "input", "change" });
var (file0Name, file0Size) = await TaskUtils.WhenAll(
Server.WaitForRequest("/upload", request => (request.Form.Files[0].FileName, request.Form.Files[0].Length)),
page.ClickAsync("input[type=submit]")
);
Assert.AreEqual("200MB", file0Name);
Assert.AreEqual(200 * 1024 * 1024, file0Size);
}
private class RemoteServer
{
private Process Process { get; set; }
public string WSEndpoint { get; set; }
internal RemoteServer(string browserName)
{
try
{
var startInfo = new ProcessStartInfo(Driver.GetExecutablePath(), $"launch-server --browser {browserName}")
{
UseShellExecute = false,
RedirectStandardOutput = true,
};
foreach (var pair in Driver.GetEnvironmentVariables())
{
startInfo.EnvironmentVariables[pair.Key] = pair.Value;
}
Process = new()
{
StartInfo = startInfo,
};
Process.Start();
WSEndpoint = Process.StandardOutput.ReadLine();
if (WSEndpoint != null && !WSEndpoint.StartsWith("ws://"))
{
throw new PlaywrightException("Invalid web socket address: " + WSEndpoint);
}
}
catch (IOException ex)
{
throw new PlaywrightException("Failed to launch server", ex);
}
}
internal void Close()
{
Process.Kill(true);
Process.WaitForExit();
WSEndpoint = null;
}
}
}
}