Skip to content

Commit 7c20703

Browse files
authored
Introduce file chooser interception (#1244)
* Some progress * Some progress * Feature complete * cr * cr * Add missing ConfigureAwait
1 parent 190cbb4 commit 7c20703

14 files changed

+628
-19
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System.Threading.Tasks;
2+
using Microsoft.AspNetCore.Http;
3+
using Xunit;
4+
using Xunit.Abstractions;
5+
6+
namespace PuppeteerSharp.Tests.ChromiumSpecificTests
7+
{
8+
[Collection("PuppeteerLoaderFixture collection")]
9+
public class PageWaitForFileChooserTests : PuppeteerPageBaseTest
10+
{
11+
public PageWaitForFileChooserTests(ITestOutputHelper output) : base(output)
12+
{
13+
}
14+
15+
[Fact]
16+
public async Task ShouldFailGracefullyWhenTryingToWorkWithFilechoosersWithinMultipleConnections()
17+
{
18+
// 1. Launch a browser and connect to all pages.
19+
var originalBrowser = await Puppeteer.LaunchAsync(TestConstants.DefaultBrowserOptions());
20+
await originalBrowser.PagesAsync();
21+
// 2. Connect a remote browser and connect to first page.
22+
var remoteBrowser = await Puppeteer.ConnectAsync(new ConnectOptions
23+
{
24+
BrowserWSEndpoint = originalBrowser.WebSocketEndpoint
25+
});
26+
var page = (await remoteBrowser.PagesAsync())[0];
27+
// 3. Make sure |page.waitForFileChooser()| does not work with multiclient.
28+
var ex = await Assert.ThrowsAsync<PuppeteerException>(() => page.WaitForFileChooserAsync());
29+
Assert.Equal("File chooser handling does not work with multiple connections to the same page", ex.Message);
30+
await originalBrowser.CloseAsync();
31+
}
32+
}
33+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using PuppeteerSharp.Mobile;
4+
using Xunit;
5+
using Xunit.Abstractions;
6+
7+
namespace PuppeteerSharp.Tests.InputTests
8+
{
9+
[Collection("PuppeteerLoaderFixture collection")]
10+
public class FileChooserAcceptTests : PuppeteerPageBaseTest
11+
{
12+
public FileChooserAcceptTests(ITestOutputHelper output) : base(output)
13+
{
14+
}
15+
16+
[Fact]
17+
public async Task ShouldWorkWhenFileInputIsAttachedToDOM()
18+
{
19+
await Page.SetContentAsync("<input type=file>");
20+
var waitForTask = Page.WaitForFileChooserAsync();
21+
22+
await Task.WhenAll(
23+
waitForTask,
24+
Page.ClickAsync("input"));
25+
26+
Assert.NotNull(waitForTask.Result);
27+
}
28+
29+
[Fact]
30+
public async Task ShouldAcceptSingleFile()
31+
{
32+
await Page.SetContentAsync("<input type=file oninput='javascript:console.timeStamp()'>");
33+
var waitForTask = Page.WaitForFileChooserAsync();
34+
var metricsTcs = new TaskCompletionSource<bool>();
35+
36+
await Task.WhenAll(
37+
waitForTask,
38+
Page.ClickAsync("input"));
39+
40+
Page.Metrics += (sender, e) => metricsTcs.TrySetResult(true);
41+
42+
await Task.WhenAll(
43+
waitForTask.Result.AcceptAsync(TestConstants.FileToUpload),
44+
metricsTcs.Task);
45+
46+
Assert.Equal(1, await Page.QuerySelectorAsync("input").EvaluateFunctionAsync<int>("input => input.files.length"));
47+
Assert.Equal(
48+
"file-to-upload.txt",
49+
await Page.QuerySelectorAsync("input").EvaluateFunctionAsync<string>("input => input.files[0].name"));
50+
}
51+
52+
[Fact]
53+
public async Task ShouldBeAbleToReadSelectedFile()
54+
{
55+
await Page.SetContentAsync("<input type=file>");
56+
_ = Page.WaitForFileChooserAsync().ContinueWith(t => t.Result.AcceptAsync(TestConstants.FileToUpload));
57+
58+
Assert.Equal(
59+
"contents of the file",
60+
await Page.QuerySelectorAsync("input").EvaluateFunctionAsync<string>(@"async picker =>
61+
{
62+
picker.click();
63+
await new Promise(x => picker.oninput = x);
64+
const reader = new FileReader();
65+
const promise = new Promise(fulfill => reader.onload = fulfill);
66+
reader.readAsText(picker.files[0]);
67+
return promise.then(() => reader.result);
68+
}"));
69+
}
70+
71+
[Fact]
72+
public async Task ShouldBeAbleToResetSelectedFilesWithEmptyFileList()
73+
{
74+
await Page.SetContentAsync("<input type=file>");
75+
_ = Page.WaitForFileChooserAsync().ContinueWith(t => t.Result.AcceptAsync(TestConstants.FileToUpload));
76+
77+
Assert.Equal(
78+
1,
79+
await Page.QuerySelectorAsync("input").EvaluateFunctionAsync<int>(@"async picker =>
80+
{
81+
picker.click();
82+
await new Promise(x => picker.oninput = x);
83+
return picker.files.length;
84+
}"));
85+
86+
_ = Page.WaitForFileChooserAsync().ContinueWith(t => t.Result.AcceptAsync());
87+
88+
Assert.Equal(
89+
0,
90+
await Page.QuerySelectorAsync("input").EvaluateFunctionAsync<int>(@"async picker =>
91+
{
92+
picker.click();
93+
await new Promise(x => picker.oninput = x);
94+
return picker.files.length;
95+
}"));
96+
}
97+
98+
[Fact]
99+
public async Task ShouldNotAcceptMultipleFilesForSingleFileInput()
100+
{
101+
await Page.SetContentAsync("<input type=file>");
102+
var waitForTask = Page.WaitForFileChooserAsync();
103+
104+
await Task.WhenAll(
105+
waitForTask,
106+
Page.ClickAsync("input"));
107+
108+
var ex = await Assert.ThrowsAsync<MessageException>(() => waitForTask.Result.AcceptAsync(
109+
"./assets/file-to-upload.txt",
110+
"./assets/pptr.png"));
111+
}
112+
113+
[Fact]
114+
public async Task ShouldFailWhenAcceptingFileChooserTwice()
115+
{
116+
await Page.SetContentAsync("<input type=file>");
117+
var waitForTask = Page.WaitForFileChooserAsync();
118+
119+
await Task.WhenAll(
120+
waitForTask,
121+
Page.ClickAsync("input"));
122+
123+
var fileChooser = waitForTask.Result;
124+
await fileChooser.AcceptAsync();
125+
var ex = await Assert.ThrowsAsync<PuppeteerException>(() => waitForTask.Result.AcceptAsync());
126+
Assert.Equal("Cannot accept FileChooser which is already handled!", ex.Message);
127+
}
128+
}
129+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using PuppeteerSharp.Mobile;
4+
using Xunit;
5+
using Xunit.Abstractions;
6+
7+
namespace PuppeteerSharp.Tests.InputTests
8+
{
9+
[Collection("PuppeteerLoaderFixture collection")]
10+
public class FileChooserCancelTests : PuppeteerPageBaseTest
11+
{
12+
public FileChooserCancelTests(ITestOutputHelper output) : base(output)
13+
{
14+
}
15+
16+
[Fact]
17+
public async Task ShouldCancelDialog()
18+
{
19+
// Consider file chooser canceled if we can summon another one.
20+
// There's no reliable way in WebPlatform to see that FileChooser was
21+
// canceled.
22+
await Page.SetContentAsync("<input type=file>");
23+
var waitForTask = Page.WaitForFileChooserAsync();
24+
25+
await Task.WhenAll(
26+
waitForTask,
27+
Page.ClickAsync("input"));
28+
29+
var fileChooser = waitForTask.Result;
30+
await fileChooser.CancelAsync();
31+
32+
await Task.WhenAll(
33+
Page.WaitForFileChooserAsync(),
34+
Page.ClickAsync("input"));
35+
}
36+
37+
[Fact]
38+
public async Task ShouldFailWhenCancelingFileChooserTwice()
39+
{
40+
await Page.SetContentAsync("<input type=file>");
41+
var waitForTask = Page.WaitForFileChooserAsync();
42+
43+
await Task.WhenAll(
44+
waitForTask,
45+
Page.ClickAsync("input"));
46+
47+
var fileChooser = waitForTask.Result;
48+
await fileChooser.CancelAsync();
49+
50+
var ex = await Assert.ThrowsAsync<PuppeteerException>(() => fileChooser.CancelAsync());
51+
Assert.Equal("Cannot accept FileChooser which is already handled!", ex.Message);
52+
}
53+
}
54+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using PuppeteerSharp.Mobile;
4+
using Xunit;
5+
using Xunit.Abstractions;
6+
7+
namespace PuppeteerSharp.Tests.InputTests
8+
{
9+
[Collection("PuppeteerLoaderFixture collection")]
10+
public class FileChooserIsMultipleTests : PuppeteerPageBaseTest
11+
{
12+
public FileChooserIsMultipleTests(ITestOutputHelper output) : base(output)
13+
{
14+
}
15+
16+
[Fact]
17+
public async Task ShouldWorkForSingleFilePick()
18+
{
19+
await Page.SetContentAsync("<input type=file>");
20+
var waitForTask = Page.WaitForFileChooserAsync();
21+
22+
await Task.WhenAll(
23+
waitForTask,
24+
Page.ClickAsync("input"));
25+
26+
Assert.False(waitForTask.Result.IsMultiple);
27+
}
28+
29+
[Fact]
30+
public async Task ShouldWorkForMultiple()
31+
{
32+
await Page.SetContentAsync("<input type=file multiple>");
33+
var waitForTask = Page.WaitForFileChooserAsync();
34+
35+
await Task.WhenAll(
36+
waitForTask,
37+
Page.ClickAsync("input"));
38+
39+
Assert.True(waitForTask.Result.IsMultiple);
40+
}
41+
42+
[Fact]
43+
public async Task ShouldWorkForWebkitDirectory()
44+
{
45+
await Page.SetContentAsync("<input type=file multiple webkitdirectory>");
46+
var waitForTask = Page.WaitForFileChooserAsync();
47+
48+
await Task.WhenAll(
49+
waitForTask,
50+
Page.ClickAsync("input"));
51+
52+
Assert.True(waitForTask.Result.IsMultiple);
53+
}
54+
}
55+
}

lib/PuppeteerSharp.Tests/InputTests/InputTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public async Task ShouldNotHangWithTouchEnabledViewports()
3838
public async Task ShouldUploadTheFile()
3939
{
4040
await Page.GoToAsync(TestConstants.ServerUrl + "/input/fileupload.html");
41-
var filePath = "./Assets/file-to-upload.txt";
41+
var filePath = TestConstants.FileToUpload;
4242
var input = await Page.QuerySelectorAsync("input");
4343
await input.UploadFileAsync(filePath);
4444
Assert.Equal("file-to-upload.txt", await Page.EvaluateFunctionAsync<string>("e => e.files[0].name", input));
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using PuppeteerSharp.Mobile;
4+
using Xunit;
5+
using Xunit.Abstractions;
6+
7+
namespace PuppeteerSharp.Tests.InputTests
8+
{
9+
[Collection("PuppeteerLoaderFixture collection")]
10+
public class PageWaitForFileChooserTests : PuppeteerPageBaseTest
11+
{
12+
public PageWaitForFileChooserTests(ITestOutputHelper output) : base(output)
13+
{
14+
}
15+
16+
[Fact]
17+
public async Task ShouldWorkWhenFileInputIsAttachedToDOM()
18+
{
19+
await Page.SetContentAsync("<input type=file>");
20+
var waitForTask = Page.WaitForFileChooserAsync();
21+
22+
await Task.WhenAll(
23+
waitForTask,
24+
Page.ClickAsync("input"));
25+
26+
Assert.NotNull(waitForTask.Result);
27+
}
28+
29+
[Fact]
30+
public async Task ShouldWorkWhenFileInputIsNotAttachedToDOM()
31+
{
32+
var waitForTask = Page.WaitForFileChooserAsync();
33+
34+
await Task.WhenAll(
35+
waitForTask,
36+
Page.EvaluateFunctionAsync(@"() =>
37+
{
38+
const el = document.createElement('input');
39+
el.type = 'file';
40+
el.click();
41+
}"));
42+
43+
Assert.NotNull(waitForTask.Result);
44+
}
45+
46+
[Fact]
47+
public async Task ShouldRespectTimeout()
48+
{
49+
var ex = await Assert.ThrowsAsync<TimeoutException>(() => Page.WaitForFileChooserAsync(new WaitForFileChooserOptions
50+
{
51+
Timeout = 1
52+
}));
53+
}
54+
55+
[Fact]
56+
public async Task ShouldRespectTimeoutWhenThereIsNoCustomTimeout()
57+
{
58+
Page.DefaultTimeout = 1;
59+
var ex = await Assert.ThrowsAsync<TimeoutException>(() => Page.WaitForFileChooserAsync());
60+
}
61+
62+
[Fact]
63+
public async Task ShouldPrioritizeExactTimeoutOverDefaultTimeout()
64+
{
65+
Page.DefaultTimeout = 0;
66+
var ex = await Assert.ThrowsAsync<TimeoutException>(() => Page.WaitForFileChooserAsync(new WaitForFileChooserOptions
67+
{
68+
Timeout = 1
69+
}));
70+
}
71+
72+
[Fact]
73+
public async Task ShouldWorkWithNoTimeout()
74+
{
75+
var waitForTask = Page.WaitForFileChooserAsync(new WaitForFileChooserOptions { Timeout = 0 });
76+
77+
await Task.WhenAll(
78+
waitForTask,
79+
Page.EvaluateFunctionAsync(@"() => setTimeout(() =>
80+
{
81+
const el = document.createElement('input');
82+
el.type = 'file';
83+
el.click();
84+
}, 50)"));
85+
Assert.NotNull(waitForTask.Result);
86+
}
87+
88+
[Fact]
89+
public async Task ShouldReturnTheSameFileChooserWhenThereAreManyWatchdogsSimultaneously()
90+
{
91+
await Page.SetContentAsync("<input type=file>");
92+
var fileChooserTask1 = Page.WaitForFileChooserAsync();
93+
var fileChooserTask2 = Page.WaitForFileChooserAsync();
94+
95+
await Task.WhenAll(
96+
fileChooserTask1,
97+
fileChooserTask2,
98+
Page.QuerySelectorAsync("input").EvaluateFunctionAsync("input => input.click()"));
99+
Assert.Same(fileChooserTask1.Result, fileChooserTask2.Result);
100+
}
101+
}
102+
}

lib/PuppeteerSharp.Tests/TestConstants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public static class TestConstants
2626
public static readonly DeviceDescriptor IPhone6Landscape = Puppeteer.Devices[DeviceDescriptorName.IPhone6Landscape];
2727

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

3031
public static readonly IEnumerable<string> NestedFramesDumpResult = new List<string>()
3132
{

0 commit comments

Comments
 (0)