Skip to content

Commit

Permalink
Introduce file chooser interception (#1244)
Browse files Browse the repository at this point in the history
* Some progress

* Some progress

* Feature complete

* cr

* cr

* Add missing ConfigureAwait
  • Loading branch information
kblok committed Aug 12, 2019
1 parent 190cbb4 commit 7c20703
Show file tree
Hide file tree
Showing 14 changed files with 628 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Xunit;
using Xunit.Abstractions;

namespace PuppeteerSharp.Tests.ChromiumSpecificTests
{
[Collection("PuppeteerLoaderFixture collection")]
public class PageWaitForFileChooserTests : PuppeteerPageBaseTest
{
public PageWaitForFileChooserTests(ITestOutputHelper output) : base(output)
{
}

[Fact]
public async Task ShouldFailGracefullyWhenTryingToWorkWithFilechoosersWithinMultipleConnections()
{
// 1. Launch a browser and connect to all pages.
var originalBrowser = await Puppeteer.LaunchAsync(TestConstants.DefaultBrowserOptions());
await originalBrowser.PagesAsync();
// 2. Connect a remote browser and connect to first page.
var remoteBrowser = await Puppeteer.ConnectAsync(new ConnectOptions
{
BrowserWSEndpoint = originalBrowser.WebSocketEndpoint
});
var page = (await remoteBrowser.PagesAsync())[0];
// 3. Make sure |page.waitForFileChooser()| does not work with multiclient.
var ex = await Assert.ThrowsAsync<PuppeteerException>(() => page.WaitForFileChooserAsync());
Assert.Equal("File chooser handling does not work with multiple connections to the same page", ex.Message);
await originalBrowser.CloseAsync();
}
}
}
129 changes: 129 additions & 0 deletions lib/PuppeteerSharp.Tests/InputTests/FileChooserAcceptTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using System;
using System.Threading.Tasks;
using PuppeteerSharp.Mobile;
using Xunit;
using Xunit.Abstractions;

namespace PuppeteerSharp.Tests.InputTests
{
[Collection("PuppeteerLoaderFixture collection")]
public class FileChooserAcceptTests : PuppeteerPageBaseTest
{
public FileChooserAcceptTests(ITestOutputHelper output) : base(output)
{
}

[Fact]
public async Task ShouldWorkWhenFileInputIsAttachedToDOM()
{
await Page.SetContentAsync("<input type=file>");
var waitForTask = Page.WaitForFileChooserAsync();

await Task.WhenAll(
waitForTask,
Page.ClickAsync("input"));

Assert.NotNull(waitForTask.Result);
}

[Fact]
public async Task ShouldAcceptSingleFile()
{
await Page.SetContentAsync("<input type=file oninput='javascript:console.timeStamp()'>");
var waitForTask = Page.WaitForFileChooserAsync();
var metricsTcs = new TaskCompletionSource<bool>();

await Task.WhenAll(
waitForTask,
Page.ClickAsync("input"));

Page.Metrics += (sender, e) => metricsTcs.TrySetResult(true);

await Task.WhenAll(
waitForTask.Result.AcceptAsync(TestConstants.FileToUpload),
metricsTcs.Task);

Assert.Equal(1, await Page.QuerySelectorAsync("input").EvaluateFunctionAsync<int>("input => input.files.length"));
Assert.Equal(
"file-to-upload.txt",
await Page.QuerySelectorAsync("input").EvaluateFunctionAsync<string>("input => input.files[0].name"));
}

[Fact]
public async Task ShouldBeAbleToReadSelectedFile()
{
await Page.SetContentAsync("<input type=file>");
_ = Page.WaitForFileChooserAsync().ContinueWith(t => t.Result.AcceptAsync(TestConstants.FileToUpload));

Assert.Equal(
"contents of the file",
await Page.QuerySelectorAsync("input").EvaluateFunctionAsync<string>(@"async picker =>
{
picker.click();
await new Promise(x => picker.oninput = x);
const reader = new FileReader();
const promise = new Promise(fulfill => reader.onload = fulfill);
reader.readAsText(picker.files[0]);
return promise.then(() => reader.result);
}"));
}

[Fact]
public async Task ShouldBeAbleToResetSelectedFilesWithEmptyFileList()
{
await Page.SetContentAsync("<input type=file>");
_ = Page.WaitForFileChooserAsync().ContinueWith(t => t.Result.AcceptAsync(TestConstants.FileToUpload));

Assert.Equal(
1,
await Page.QuerySelectorAsync("input").EvaluateFunctionAsync<int>(@"async picker =>
{
picker.click();
await new Promise(x => picker.oninput = x);
return picker.files.length;
}"));

_ = Page.WaitForFileChooserAsync().ContinueWith(t => t.Result.AcceptAsync());

Assert.Equal(
0,
await Page.QuerySelectorAsync("input").EvaluateFunctionAsync<int>(@"async picker =>
{
picker.click();
await new Promise(x => picker.oninput = x);
return picker.files.length;
}"));
}

[Fact]
public async Task ShouldNotAcceptMultipleFilesForSingleFileInput()
{
await Page.SetContentAsync("<input type=file>");
var waitForTask = Page.WaitForFileChooserAsync();

await Task.WhenAll(
waitForTask,
Page.ClickAsync("input"));

var ex = await Assert.ThrowsAsync<MessageException>(() => waitForTask.Result.AcceptAsync(
"./assets/file-to-upload.txt",
"./assets/pptr.png"));
}

[Fact]
public async Task ShouldFailWhenAcceptingFileChooserTwice()
{
await Page.SetContentAsync("<input type=file>");
var waitForTask = Page.WaitForFileChooserAsync();

await Task.WhenAll(
waitForTask,
Page.ClickAsync("input"));

var fileChooser = waitForTask.Result;
await fileChooser.AcceptAsync();
var ex = await Assert.ThrowsAsync<PuppeteerException>(() => waitForTask.Result.AcceptAsync());
Assert.Equal("Cannot accept FileChooser which is already handled!", ex.Message);
}
}
}
54 changes: 54 additions & 0 deletions lib/PuppeteerSharp.Tests/InputTests/FileChooserCancelTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Threading.Tasks;
using PuppeteerSharp.Mobile;
using Xunit;
using Xunit.Abstractions;

namespace PuppeteerSharp.Tests.InputTests
{
[Collection("PuppeteerLoaderFixture collection")]
public class FileChooserCancelTests : PuppeteerPageBaseTest
{
public FileChooserCancelTests(ITestOutputHelper output) : base(output)
{
}

[Fact]
public async Task ShouldCancelDialog()
{
// Consider file chooser canceled if we can summon another one.
// There's no reliable way in WebPlatform to see that FileChooser was
// canceled.
await Page.SetContentAsync("<input type=file>");
var waitForTask = Page.WaitForFileChooserAsync();

await Task.WhenAll(
waitForTask,
Page.ClickAsync("input"));

var fileChooser = waitForTask.Result;
await fileChooser.CancelAsync();

await Task.WhenAll(
Page.WaitForFileChooserAsync(),
Page.ClickAsync("input"));
}

[Fact]
public async Task ShouldFailWhenCancelingFileChooserTwice()
{
await Page.SetContentAsync("<input type=file>");
var waitForTask = Page.WaitForFileChooserAsync();

await Task.WhenAll(
waitForTask,
Page.ClickAsync("input"));

var fileChooser = waitForTask.Result;
await fileChooser.CancelAsync();

var ex = await Assert.ThrowsAsync<PuppeteerException>(() => fileChooser.CancelAsync());
Assert.Equal("Cannot accept FileChooser which is already handled!", ex.Message);
}
}
}
55 changes: 55 additions & 0 deletions lib/PuppeteerSharp.Tests/InputTests/FileChooserIsMultipleTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using System.Threading.Tasks;
using PuppeteerSharp.Mobile;
using Xunit;
using Xunit.Abstractions;

namespace PuppeteerSharp.Tests.InputTests
{
[Collection("PuppeteerLoaderFixture collection")]
public class FileChooserIsMultipleTests : PuppeteerPageBaseTest
{
public FileChooserIsMultipleTests(ITestOutputHelper output) : base(output)
{
}

[Fact]
public async Task ShouldWorkForSingleFilePick()
{
await Page.SetContentAsync("<input type=file>");
var waitForTask = Page.WaitForFileChooserAsync();

await Task.WhenAll(
waitForTask,
Page.ClickAsync("input"));

Assert.False(waitForTask.Result.IsMultiple);
}

[Fact]
public async Task ShouldWorkForMultiple()
{
await Page.SetContentAsync("<input type=file multiple>");
var waitForTask = Page.WaitForFileChooserAsync();

await Task.WhenAll(
waitForTask,
Page.ClickAsync("input"));

Assert.True(waitForTask.Result.IsMultiple);
}

[Fact]
public async Task ShouldWorkForWebkitDirectory()
{
await Page.SetContentAsync("<input type=file multiple webkitdirectory>");
var waitForTask = Page.WaitForFileChooserAsync();

await Task.WhenAll(
waitForTask,
Page.ClickAsync("input"));

Assert.True(waitForTask.Result.IsMultiple);
}
}
}
2 changes: 1 addition & 1 deletion lib/PuppeteerSharp.Tests/InputTests/InputTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public async Task ShouldNotHangWithTouchEnabledViewports()
public async Task ShouldUploadTheFile()
{
await Page.GoToAsync(TestConstants.ServerUrl + "/input/fileupload.html");
var filePath = "./Assets/file-to-upload.txt";
var filePath = TestConstants.FileToUpload;
var input = await Page.QuerySelectorAsync("input");
await input.UploadFileAsync(filePath);
Assert.Equal("file-to-upload.txt", await Page.EvaluateFunctionAsync<string>("e => e.files[0].name", input));
Expand Down
102 changes: 102 additions & 0 deletions lib/PuppeteerSharp.Tests/InputTests/PageWaitForFileChooserTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System;
using System.Threading.Tasks;
using PuppeteerSharp.Mobile;
using Xunit;
using Xunit.Abstractions;

namespace PuppeteerSharp.Tests.InputTests
{
[Collection("PuppeteerLoaderFixture collection")]
public class PageWaitForFileChooserTests : PuppeteerPageBaseTest
{
public PageWaitForFileChooserTests(ITestOutputHelper output) : base(output)
{
}

[Fact]
public async Task ShouldWorkWhenFileInputIsAttachedToDOM()
{
await Page.SetContentAsync("<input type=file>");
var waitForTask = Page.WaitForFileChooserAsync();

await Task.WhenAll(
waitForTask,
Page.ClickAsync("input"));

Assert.NotNull(waitForTask.Result);
}

[Fact]
public async Task ShouldWorkWhenFileInputIsNotAttachedToDOM()
{
var waitForTask = Page.WaitForFileChooserAsync();

await Task.WhenAll(
waitForTask,
Page.EvaluateFunctionAsync(@"() =>
{
const el = document.createElement('input');
el.type = 'file';
el.click();
}"));

Assert.NotNull(waitForTask.Result);
}

[Fact]
public async Task ShouldRespectTimeout()
{
var ex = await Assert.ThrowsAsync<TimeoutException>(() => Page.WaitForFileChooserAsync(new WaitForFileChooserOptions
{
Timeout = 1
}));
}

[Fact]
public async Task ShouldRespectTimeoutWhenThereIsNoCustomTimeout()
{
Page.DefaultTimeout = 1;
var ex = await Assert.ThrowsAsync<TimeoutException>(() => Page.WaitForFileChooserAsync());
}

[Fact]
public async Task ShouldPrioritizeExactTimeoutOverDefaultTimeout()
{
Page.DefaultTimeout = 0;
var ex = await Assert.ThrowsAsync<TimeoutException>(() => Page.WaitForFileChooserAsync(new WaitForFileChooserOptions
{
Timeout = 1
}));
}

[Fact]
public async Task ShouldWorkWithNoTimeout()
{
var waitForTask = Page.WaitForFileChooserAsync(new WaitForFileChooserOptions { Timeout = 0 });

await Task.WhenAll(
waitForTask,
Page.EvaluateFunctionAsync(@"() => setTimeout(() =>
{
const el = document.createElement('input');
el.type = 'file';
el.click();
}, 50)"));
Assert.NotNull(waitForTask.Result);
}

[Fact]
public async Task ShouldReturnTheSameFileChooserWhenThereAreManyWatchdogsSimultaneously()
{
await Page.SetContentAsync("<input type=file>");
var fileChooserTask1 = Page.WaitForFileChooserAsync();
var fileChooserTask2 = Page.WaitForFileChooserAsync();

await Task.WhenAll(
fileChooserTask1,
fileChooserTask2,
Page.QuerySelectorAsync("input").EvaluateFunctionAsync("input => input.click()"));
Assert.Same(fileChooserTask1.Result, fileChooserTask2.Result);
}
}
}
1 change: 1 addition & 0 deletions lib/PuppeteerSharp.Tests/TestConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public static class TestConstants
public static readonly DeviceDescriptor IPhone6Landscape = Puppeteer.Devices[DeviceDescriptorName.IPhone6Landscape];

public static ILoggerFactory LoggerFactory { get; private set; }
public static string FileToUpload => Path.Combine(Directory.GetCurrentDirectory(), "Assets", "file-to-upload.txt");

public static readonly IEnumerable<string> NestedFramesDumpResult = new List<string>()
{
Expand Down
Loading

0 comments on commit 7c20703

Please sign in to comment.