using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using App.Core.Helpers;
using App.Core.Models;
using App.Core.Models.Configuration;
using App.Core.Models.Options;
using App.Core.Services.Configuration;
using Flurl;
using Microsoft.Extensions.Logging;
using Microsoft.Playwright;
using Newtonsoft.Json;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
namespace App.Services
{
public interface IProcessService
{
Task Process(RunOptions runOptions);
}
public class ProcessService : IProcessService
{
private static ILogger<ProcessService> _logger;
private static FileSettings _fileSettings;
private static SearchSettings _searchSettings;
private static readonly Regex ResultCountRegex = new("(?<ResultCount>\\d+)");
//private static readonly Regex NoLocationRegex = new Regex("(?<Location>\\.+)[\\d+] miles away");
private static readonly Regex PriceRegex = new("(?<Price>£\\d+,\\d+)");
//private static readonly Regex MileageRegex = new("(?<Mileage>\\d+,\\d+)");
private static readonly string AssetsDir = Path.Combine(Environment.CurrentDirectory, "Assets");
private static string _runDir;
private static DateTime _runDateTime;
public ProcessService(ILogger<ProcessService> logger, IFileSettingsService fileSettingsSvc, ISearchSettingsService searchSettingsSvc)
{
_logger = logger;
_fileSettings = fileSettingsSvc.GetSettings();
_searchSettings = searchSettingsSvc.GetSettings();
_runDateTime = DateTime.Now;
}
public async Task Process(RunOptions options)
{
await SetupRun(options);
try
{
foreach (var searchConfig in _searchSettings.Searches)
{
// set up the file system for the search
string searchDirectory = SetupSearch(searchConfig);
// scrape autotrader for this search config
Results results = await Scrape(searchConfig, searchDirectory);
// write all the scraped results as a json log file
await File.WriteAllTextAsync(Path.Combine(searchDirectory, "results.json"), JsonConvert.SerializeObject(results, Formatting.Indented));
// create the web assets for this search config
await BuildMapScript(results, searchDirectory);
}
}
catch (Exception e)
{
_logger?.LogError(e, "Scraping failed");
}
}
/// <summary>
/// Scrape Autotrader for advert details
/// </summary>
/// <param name="searchConfig"></param>
/// <param name="searchDirectory"></param>
/// <returns></returns>
private async Task<Results> Scrape(SearchConfig searchConfig, string searchDirectory)
{
// build the search URL
string searchUrl = Url.Combine(_searchSettings.Domain, searchConfig.Url);
// set up playwright and go to autotrader
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Firefox.LaunchAsync();
var page = await browser.NewPageAsync();
await page.GotoAsync(searchUrl);
// get result count for validation
IElementHandle resultEls = await page.QuerySelectorAsync(".search-form__count");
int resultsCount = 0;
int pages = resultEls != null
? int.TryParse(ResultCountRegex.Match(await resultEls.InnerTextAsync()).Groups["ResultCount"].Value, out resultsCount)
? (int)Math.Ceiling((decimal)resultsCount / 10)
: 1
: 1;
var results = new Results {Expected = resultsCount};
_logger?.LogInformation("Expecting {ResultsCount} results", resultsCount);
// iterate pages of adverts
for (var i = 1; i <= pages; i++)
{
_logger?.LogInformation("Processing page {Page}", i);
// if this isn't the first page then append the page number and navigate
if (i > 1)
{
var navPage = searchUrl.SetQueryParam("page", i);
results.Pages.Add(navPage);
await page.GotoAsync(navPage);
}
else
results.Pages.Add(searchUrl);
// get adverts on page
var ads = await page.QuerySelectorAllAsync("article.product-card[data-standout-type=''] > a");
// iterate all adverts
foreach (var ad in ads)
{
// build a car object
var car = new Car(await ad.GetAttributeAsync("href"));
_logger?.LogInformation("Processing ad {Url}", car.Url);
// open the advert in a new browser page
IPage adPage = await browser.NewPageAsync();
await adPage.GotoAsync(car.Url.AsFullUrl(_searchSettings.Domain));
// wait 1 second to try and improve image quality of snapshot
Thread.Sleep(1000);
// snapshot entire ad
var adSelector = ":is(main > div > div:nth-child(2), main > article > div:nth-child(2), main > div > div)";
if (await ElementExists(adSelector, adPage, "ad image", false))
{
IElementHandle image = await adPage.QuerySelectorAsync(adSelector);
if (image == null)
_logger?.LogError("Failed to retrieve ad image after successfully finding the ad image element");
else
{
var imageDir = Path.Combine(searchDirectory, "images");
car.AdImage = $"{car.Id}_ad.png";
var fullSizePath = Path.Combine(imageDir, $"{car.Id}_ad_full.png");
await image.ScreenshotAsync(new ElementHandleScreenshotOptions { Path = fullSizePath });
await using var input = File.OpenRead(fullSizePath);
using var img = await Image.LoadAsync(input);
img.Mutate(r => r.Resize(new ResizeOptions
{
Size = new Size(img.Width, img.Width),
Mode = ResizeMode.Crop,
Position = AnchorPositionMode.Top
}));
await img.SaveAsync(Path.Combine(imageDir, car.AdImage));
}
}
#region attempts to get more granular data, not required when snapshotting entire ad
// snapshot image
// var imgSelector = ":is(.gallery__container, section[data-gui='gallery-section'])";
//
// if (await ElementExists(imgSelector, adPage, "image", false))
// {
// IElementHandle image = await page.QuerySelectorAsync(imgSelector);
//
// if (image == null)
// _logger.LogError("Failed to retrieve image after successfully finding the image element");
// else
// {
// car.Image = $"{car.Id}.png";
//
// await image.ScreenshotAsync(new ElementHandleScreenshotOptions { Path = Path.Combine(_imageDir, car.Image) });
// }
// }
// get price
// var priceSelector = "text=/£\\d{2},\\d{3}/i";
//
// if (await ElementExists(priceSelector, adPage, "price", false))
// {
// IElementHandle price = await adPage.QuerySelectorAsync(priceSelector);
//
// if (price == null)
// _logger.LogError("Failed to retrieve price after successfully finding the price element");
// else
// car.Price = PriceRegex.Match(await price.InnerHTMLAsync()).Groups["Price"].Value;
// }
// get mileage
// var mileageSelector = "text=/\\d+,\\d+ miles/i";
//
// if (await ElementExists(mileageSelector, adPage, "mileage", false))
// {
// IElementHandle mileage = await adPage.QuerySelectorAsync(mileageSelector);
//
// if (mileage == null)
// _logger.LogError("Failed to retrieve mileage after successfully finding the mileage element");
// else
// car.Mileage = await mileage.InnerTextAsync();
// }
#endregion
bool locationLinkFound = false;
// try to find a location link
try
{
var locBtnSelector = ":is(button[data-gui-test='dealerLocationLink'], button.seller-location__toggle)";
if (!await ElementExists(locBtnSelector, adPage, "location"))
{
results.Cars.Add(car);
continue;
}
await adPage.ClickAsync(locBtnSelector);
var mapFrameSelector = "iframe[src^='https://www.google.com/maps/embed']";
if (!await ElementExists(mapFrameSelector, adPage, "map frame", false))
{
results.Cars.Add(car);
continue;
}
Thread.Sleep(500);
var mapFrameQuery = await adPage.QuerySelectorAsync(mapFrameSelector);
if (mapFrameQuery == null)
{
_logger?.LogError("Failed to find the map iframe after successfully finding the map frame element");
results.Cars.Add(car);
await adPage.CloseAsync();
continue;
}
var mapFrame = await mapFrameQuery.ContentFrameAsync();
if (mapFrame == null)
{
_logger?.LogError("Failed to select the map iframe element after successfully finding the map iframe element");
results.Cars.Add(car);
await adPage.CloseAsync();
continue;
}
var coordsSelector = "div.place-name";
if (await ElementExists(coordsSelector, mapFrame, "coordinates"))
{
IElementHandle coords = await mapFrame.QuerySelectorAsync(coordsSelector);
if (coords == null)
_logger?.LogError("Failed to retrieve coordinates after successfully finding the coordinates element");
else
car.Coords = await coords.InnerTextAsync();
}
var addressSelector = "div.address";
if (await ElementExists(coordsSelector, mapFrame, "address"))
{
IElementHandle address = await mapFrame.QuerySelectorAsync(addressSelector);
if (address == null)
_logger?.LogError("Failed to retrieve address after successfully finding the address element");
else
car.Location = await address.InnerTextAsync();
}
locationLinkFound = true;
}
catch (Exception e)
{
_logger?.LogWarning(e, "Failed to find a location link for advert");
locationLinkFound = false;
}
// todo - if a location link wasn't found, ust the approximate location from the summary screen
if (!locationLinkFound)
{
}
car.Success = true;
results.Cars.Add(car);
await adPage.CloseAsync();
}
}
return results;
}
/// <summary>
/// Set up the run directory
/// </summary>
private async Task SetupRun(RunOptions options)
{
// allow output directory to be replaced with a command argument, fallback to config file setting
string output;
if (string.IsNullOrWhiteSpace(options.OutputDirectory))
{
output = _fileSettings.RunsDirectory;
_runDir = Path.Combine(_fileSettings.RunsDirectory, $"{DateTime.Now:yyyyMMdd HHmmss}");
}
else
{
output = _runDir = options.OutputDirectory;
}
// create the output directory if it doesn't exist
if (!Directory.Exists(output))
Directory.CreateDirectory(output);
if (!Directory.Exists(_runDir))
Directory.CreateDirectory(_runDir);
foreach (var file in Directory.GetFiles(AssetsDir))
{
File.Copy(file, Path.Combine(_runDir, Path.GetFileName(file)));
}
// dashboard
var dashboard = Path.Combine(_runDir, "index.html");
await File.WriteAllTextAsync(dashboard, (await File.ReadAllTextAsync(dashboard))
.Replace("//SEARCHES_ARRAY_PLACEHOLDER", _searchSettings.SearchesJsonArray())
.Replace("//RUN_DATETIME", _runDateTime.ToString("yyyy-MM-dd"))
.Replace("//RUN_DAY", _runDateTime.ToString("dddd"))
.Replace("//RUN_DATE", _runDateTime.ToString("dd"))
.Replace("//RUN_MONTH", _runDateTime.ToString("MM"))
.Replace("//RUN_YEAR", _runDateTime.ToString("yyyy")));
}
/// <summary>
/// Set up a search directory
/// </summary>
private string SetupSearch(SearchConfig searchConfig)
{
var searchDirectory = Path.Combine(_runDir, searchConfig.Id);
Directory.CreateDirectory(searchDirectory);
Directory.CreateDirectory(Path.Combine(searchDirectory, "images"));
foreach (var file in Directory.GetFiles(Path.Combine(AssetsDir, "MapTemplate")))
{
File.Copy(file, Path.Combine(searchDirectory, Path.GetFileName(file)));
}
return searchDirectory;
}
/// <summary>
/// Build Google map script
/// </summary>
/// <param name="results"></param>
/// <param name="searchDirectory"></param>
private async Task BuildMapScript(Results results, string searchDirectory)
{
var indexJsFile = Path.Combine(searchDirectory, "index.js");
await File.WriteAllTextAsync(indexJsFile, (await File.ReadAllTextAsync(indexJsFile))
.Replace("//CARS_ARRAY_PLACEHOLDER", results.JsCarsArray)
.Replace("//MARKERS_ARRAY_PLACEHOLDER", results.JsMarkers));
}
/// <summary>
/// Checks that an element exists in a page
/// </summary>
/// <param name="selector"></param>
/// <param name="page"></param>
/// <param name="selectorType"></param>
/// <param name="closePage"></param>
/// <returns></returns>
private async Task<bool> ElementExists(string selector, IPage page, string selectorType, bool closePage = true)
{
try
{
await page.WaitForSelectorAsync(selector, new PageWaitForSelectorOptions{Timeout = 3000});
return true;
}
catch (Exception e)
{
//await page.PauseAsync();
_logger?.LogError(e, "Unable to find {SelectorType} for advert at {PageUrl}", selectorType, page.Url.AsFullUrl(_searchSettings.Domain));
if (closePage)
await page.CloseAsync();
return false;
}
}
/// <summary>
/// Checks that an element exists in a frame
/// </summary>
/// <param name="selector"></param>
/// <param name="frame"></param>
/// <param name="selectorType"></param>
/// <returns></returns>
private async Task<bool> ElementExists(string selector, IFrame frame, string selectorType)
{
try
{
await frame.WaitForSelectorAsync(selector, new FrameWaitForSelectorOptions{Timeout = 3000});
return true;
}
catch (Exception e)
{
_logger?.LogError(e, "Unable to find {SelectorType} for advert at {FrameUrl}", selectorType, frame.Url);
return false;
}
}
}
}
/*
* 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) 2020 DarÃo Kondratiuk
* 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.Threading.Tasks;
using Microsoft.Playwright.Helpers;
using Microsoft.Playwright.Transport.Channels;
using Microsoft.Playwright.Transport.Protocol;
namespace Microsoft.Playwright.Core
{
internal partial class ElementHandle : JSHandle, IElementHandle, IChannelOwner<ElementHandle>
{
private readonly ElementHandleChannel _channel;
internal ElementHandle(IChannelOwner parent, string guid, ElementHandleInitializer initializer) : base(parent, guid, initializer)
{
_channel = new(guid, parent.Connection, this);
_channel.PreviewUpdated += (_, e) => Preview = e.Preview;
}
ChannelBase IChannelOwner.Channel => _channel;
IChannel<ElementHandle> IChannelOwner<ElementHandle>.Channel => _channel;
internal IChannel<ElementHandle> ElementChannel => _channel;
public async Task<IElementHandle> WaitForSelectorAsync(string selector, ElementHandleWaitForSelectorOptions options = default)
=> (await _channel.WaitForSelectorAsync(
selector: selector,
state: options?.State,
timeout: options?.Timeout,
strict: options?.Strict).ConfigureAwait(false))?.Object;
public Task WaitForElementStateAsync(ElementState state, ElementHandleWaitForElementStateOptions options = default)
=> _channel.WaitForElementStateAsync(state, timeout: options?.Timeout);
public Task PressAsync(string key, ElementHandlePressOptions options = default)
=> _channel.PressAsync(
key,
delay: options?.Delay,
timeout: options?.Timeout,
noWaitAfter: options?.NoWaitAfter);
public Task TypeAsync(string text, ElementHandleTypeOptions options = default)
=> _channel.TypeAsync(text, delay: options?.Delay, timeout: options?.Timeout, noWaitAfter: options?.NoWaitAfter);
public async Task<byte[]> ScreenshotAsync(ElementHandleScreenshotOptions options = default)
{
options ??= new();
if (options.Type == null && !string.IsNullOrEmpty(options.Path))
{
options.Type = DetermineScreenshotType(options.Path);
}
byte[] result = await _channel.ScreenshotAsync(
options.Path,
options.OmitBackground,
options.Type,
options.Quality,
options.Mask,
options.Animations,
options.Caret,
options.Scale,
options.Timeout).ConfigureAwait(false);
if (!string.IsNullOrEmpty(options.Path))
{
Directory.CreateDirectory(new FileInfo(options.Path).Directory.FullName);
File.WriteAllBytes(options.Path, result);
}
return result;
}
public Task FillAsync(string value, ElementHandleFillOptions options = default)
=> _channel.FillAsync(
value,
noWaitAfter: options?.NoWaitAfter,
force: options?.Force,
timeout: options?.Timeout);
public async Task<IFrame> ContentFrameAsync() => (await _channel.ContentFrameAsync().ConfigureAwait(false))?.Object;
public Task HoverAsync(ElementHandleHoverOptions options = default)
=> _channel.HoverAsync(
modifiers: options?.Modifiers,
position: options?.Position,
timeout: options?.Timeout,
force: options?.Force,
trial: options?.Trial);
public Task ScrollIntoViewIfNeededAsync(ElementHandleScrollIntoViewIfNeededOptions options = default)
=> _channel.ScrollIntoViewIfNeededAsync(options?.Timeout);
public async Task<IFrame> OwnerFrameAsync() => (await _channel.OwnerFrameAsync().ConfigureAwait(false)).Object;
public Task<ElementHandleBoundingBoxResult> BoundingBoxAsync() => _channel.BoundingBoxAsync();
public Task ClickAsync(ElementHandleClickOptions options = default)
=> _channel.ClickAsync(
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);
public Task DblClickAsync(ElementHandleDblClickOptions options = default)
=> _channel.DblClickAsync(
delay: options?.Delay,
button: options?.Button,
modifiers: options?.Modifiers,
position: options?.Position,
timeout: options?.Timeout,
force: options?.Force,
noWaitAfter: options?.NoWaitAfter,
trial: options?.Trial);
public Task SetInputFilesAsync(string files, ElementHandleSetInputFilesOptions options = default)
=> SetInputFilesAsync(new[] { files }, options);
public async Task SetInputFilesAsync(IEnumerable<string> files, ElementHandleSetInputFilesOptions options = default)
{
var frame = await OwnerFrameAsync().ConfigureAwait(false);
if (frame == null)
{
throw new PlaywrightException("Cannot set input files to detached element.");
}
var converted = await SetInputFilesHelpers.ConvertInputFilesAsync(files, (BrowserContext)frame.Page.Context).ConfigureAwait(false);
if (converted.Files != null)
{
await _channel.SetInputFilesAsync(converted.Files, options?.NoWaitAfter, options?.Timeout).ConfigureAwait(false);
}
else
{
await _channel.SetInputFilePathsAsync(converted?.LocalPaths, converted?.Streams, options?.NoWaitAfter, options?.Timeout).ConfigureAwait(false);
}
}
public Task SetInputFilesAsync(FilePayload files, ElementHandleSetInputFilesOptions options = default)
=> SetInputFilesAsync(new[] { files }, options);
public async Task SetInputFilesAsync(IEnumerable<FilePayload> files, ElementHandleSetInputFilesOptions options = default)
{
var converted = SetInputFilesHelpers.ConvertInputFiles(files);
await _channel.SetInputFilesAsync(converted.Files, options?.NoWaitAfter, options?.Timeout).ConfigureAwait(false);
}
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(e => ((ElementHandleChannel)e).Object).ToList().AsReadOnly();
public async Task<JsonElement?> EvalOnSelectorAsync(string selector, string expression, object arg = null)
=> ScriptsHelper.ParseEvaluateResult<JsonElement?>(await _channel.EvalOnSelectorAsync(
selector: selector,
script: expression,
arg: ScriptsHelper.SerializedArgument(arg)).ConfigureAwait(false));
public async Task<T> EvalOnSelectorAsync<T>(string selector, string expression, object arg = null)
=> ScriptsHelper.ParseEvaluateResult<T>(await _channel.EvalOnSelectorAsync(
selector: selector,
script: expression,
arg: ScriptsHelper.SerializedArgument(arg)).ConfigureAwait(false));
public async Task<T> EvalOnSelectorAllAsync<T>(string selector, string expression, object arg = null)
=> ScriptsHelper.ParseEvaluateResult<T>(await _channel.EvalOnSelectorAllAsync(
selector: selector,
script: expression,
arg: ScriptsHelper.SerializedArgument(arg)).ConfigureAwait(false));
public Task FocusAsync() => _channel.FocusAsync();
public Task DispatchEventAsync(string type, object eventInit = null)
=> _channel.DispatchEventAsync(
type,
eventInit = ScriptsHelper.SerializedArgument(eventInit));
public Task<string> GetAttributeAsync(string name) => _channel.GetAttributeAsync(name);
public Task<string> InnerHTMLAsync() => _channel.InnerHTMLAsync();
public Task<string> InnerTextAsync() => _channel.InnerTextAsync();
public Task<string> TextContentAsync() => _channel.TextContentAsync();
public Task SelectTextAsync(ElementHandleSelectTextOptions options = default)
=> _channel.SelectTextAsync(options?.Force, options?.Timeout);
public Task<IReadOnlyList<string>> SelectOptionAsync(string values, ElementHandleSelectOptionOptions options = default)
=> _channel.SelectOptionAsync(new[] { new SelectOptionValue() { Value = values } }, options?.NoWaitAfter, options?.Force, options?.Timeout);
public Task<IReadOnlyList<string>> SelectOptionAsync(IElementHandle values, ElementHandleSelectOptionOptions options = default)
=> _channel.SelectOptionAsync(new[] { values }, options?.NoWaitAfter, options?.Force, options?.Timeout);
public Task<IReadOnlyList<string>> SelectOptionAsync(IEnumerable<string> values, ElementHandleSelectOptionOptions options = default)
=> _channel.SelectOptionAsync(values.Select(x => new SelectOptionValue() { Value = x }), options?.NoWaitAfter, options?.Force, options?.Timeout);
public Task<IReadOnlyList<string>> SelectOptionAsync(SelectOptionValue values, ElementHandleSelectOptionOptions options = default)
=> _channel.SelectOptionAsync(new[] { values }, options?.NoWaitAfter, options?.Force, options?.Timeout);
public Task<IReadOnlyList<string>> SelectOptionAsync(IEnumerable<IElementHandle> values, ElementHandleSelectOptionOptions options = default)
=> _channel.SelectOptionAsync(values, options?.NoWaitAfter, options?.Force, options?.Timeout);
public Task<IReadOnlyList<string>> SelectOptionAsync(IEnumerable<SelectOptionValue> values, ElementHandleSelectOptionOptions options = default)
=> _channel.SelectOptionAsync(values, options?.NoWaitAfter, options?.Force, options?.Timeout);
public Task CheckAsync(ElementHandleCheckOptions options = default)
=> _channel.CheckAsync(
position: options?.Position,
timeout: options?.Timeout,
force: options?.Force,
noWaitAfter: options?.NoWaitAfter,
trial: options?.Trial);
public Task UncheckAsync(ElementHandleUncheckOptions options = default)
=> _channel.UncheckAsync(
position: options?.Position,
timeout: options?.Timeout,
force: options?.Force,
noWaitAfter: options?.NoWaitAfter,
trial: options?.Trial);
public Task TapAsync(ElementHandleTapOptions options = default)
=> _channel.TapAsync(
position: options?.Position,
modifiers: options?.Modifiers,
timeout: options?.Timeout,
force: options?.Force,
noWaitAfter: options?.NoWaitAfter,
trial: options?.Trial);
public Task<bool> IsCheckedAsync() => _channel.IsCheckedAsync();
public Task<bool> IsDisabledAsync() => _channel.IsDisabledAsync();
public Task<bool> IsEditableAsync() => _channel.IsEditableAsync();
public Task<bool> IsEnabledAsync() => _channel.IsEnabledAsync();
public Task<bool> IsHiddenAsync() => _channel.IsHiddenAsync();
public Task<bool> IsVisibleAsync() => _channel.IsVisibleAsync();
public Task<string> InputValueAsync(ElementHandleInputValueOptions options = null)
=> _channel.InputValueAsync(options?.Timeout);
public Task SetCheckedAsync(bool checkedState, ElementHandleSetCheckedOptions options = null)
=> checkedState
? _channel.CheckAsync(
position: options?.Position,
timeout: options?.Timeout,
force: options?.Force,
noWaitAfter: options?.NoWaitAfter,
trial: options?.Trial)
: _channel.UncheckAsync(
position: options?.Position,
timeout: options?.Timeout,
force: options?.Force,
noWaitAfter: options?.NoWaitAfter,
trial: options?.Trial);
internal static ScreenshotType DetermineScreenshotType(string path)
{
string mimeType = path.MimeType();
return mimeType switch
{
"image/png" => ScreenshotType.Png,
"image/jpeg" => ScreenshotType.Jpeg,
_ => throw new ArgumentException($"path: unsupported mime type \"{mimeType}\""),
};
}
}
}