Skip to content

Commit 7b5cb82

Browse files
kblokMeir017
authored andcommitted
Page geolocation (#661)
* Geolocation options * Implement BrowserContext.OverridePermissionsAsync * No need for docs having enums * Some progress * cr * Improve serialization * CodeFactor * Coolify enums * cr
1 parent c01a17e commit 7b5cb82

File tree

8 files changed

+342
-8
lines changed

8 files changed

+342
-8
lines changed

lib/PuppeteerSharp.Tests/BrowserContextTests/BrowserContextTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public async Task ShouldHaveDefaultContext()
2222
var defaultContext = Browser.BrowserContexts()[0];
2323
Assert.False(defaultContext.IsIncognito);
2424
var exception = await Assert.ThrowsAsync<PuppeteerException>(defaultContext.CloseAsync);
25+
Assert.Same(defaultContext, Browser.DefaultContext);
2526
Assert.Contains("cannot be closed", exception.Message);
2627
}
2728

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Threading.Tasks;
5+
using Xunit;
6+
using Xunit.Abstractions;
7+
8+
namespace PuppeteerSharp.Tests.PageTests
9+
{
10+
[Collection("PuppeteerLoaderFixture collection")]
11+
public class BrowserContextOverridePermissionsTests : PuppeteerPageBaseTest
12+
{
13+
public BrowserContextOverridePermissionsTests(ITestOutputHelper output) : base(output)
14+
{
15+
}
16+
17+
private Task<string> GetPermissionAsync(Page page, string name)
18+
=> page.EvaluateFunctionAsync<string>(
19+
"name => navigator.permissions.query({ name }).then(result => result.state)",
20+
name);
21+
22+
[Fact]
23+
public async Task ShouldBePromptByDefault()
24+
{
25+
await Page.GoToAsync(TestConstants.EmptyPage);
26+
Assert.Equal("prompt", await GetPermissionAsync(Page, "geolocation"));
27+
}
28+
29+
[Fact]
30+
public async Task ShouldDenyPermissionWhenNotListed()
31+
{
32+
await Page.GoToAsync(TestConstants.EmptyPage);
33+
await Context.OverridePermissionsAsync(TestConstants.EmptyPage, new OverridePermission[] { });
34+
Assert.Equal("denied", await GetPermissionAsync(Page, "geolocation"));
35+
}
36+
37+
[Fact]
38+
public async Task ShouldGrantPermissionWwhenListed()
39+
{
40+
await Page.GoToAsync(TestConstants.EmptyPage);
41+
await Context.OverridePermissionsAsync(TestConstants.EmptyPage, new OverridePermission[]
42+
{
43+
OverridePermission.Geolocation
44+
});
45+
Assert.Equal("granted", await GetPermissionAsync(Page, "geolocation"));
46+
}
47+
48+
[Fact]
49+
public async Task ShouldResetPermissions()
50+
{
51+
await Page.GoToAsync(TestConstants.EmptyPage);
52+
await Context.OverridePermissionsAsync(TestConstants.EmptyPage, new OverridePermission[]
53+
{
54+
OverridePermission.Geolocation
55+
});
56+
Assert.Equal("granted", await GetPermissionAsync(Page, "geolocation"));
57+
await Context.ClearPermissionOverridesAsync();
58+
Assert.Equal("prompt", await GetPermissionAsync(Page, "geolocation"));
59+
}
60+
61+
[Fact]
62+
public async Task ShouldTriggerPermissionOnchange()
63+
{
64+
await Page.GoToAsync(TestConstants.EmptyPage);
65+
await Page.EvaluateFunctionAsync(@"() => {
66+
window.events = [];
67+
return navigator.permissions.query({ name: 'clipboard-read'}).then(function(result) {
68+
window.events.push(result.state);
69+
result.onchange = function() {
70+
window.events.push(result.state);
71+
};
72+
});
73+
}");
74+
Assert.Equal(new string[] { "prompt" }, await Page.EvaluateExpressionAsync<string[]>("window.events"));
75+
await Context.OverridePermissionsAsync(TestConstants.EmptyPage, new OverridePermission[] { });
76+
Assert.Equal(new string[] { "prompt", "denied" }, await Page.EvaluateExpressionAsync<string[]>("window.events"));
77+
await Context.OverridePermissionsAsync(TestConstants.EmptyPage, new OverridePermission[]
78+
{
79+
OverridePermission.ClipboardRead
80+
});
81+
Assert.Equal(
82+
new string[] { "prompt", "denied", "granted" },
83+
await Page.EvaluateExpressionAsync<string[]>("window.events"));
84+
await Context.ClearPermissionOverridesAsync();
85+
Assert.Equal(
86+
new string[] { "prompt", "denied", "granted", "prompt" },
87+
await Page.EvaluateExpressionAsync<string[]>("window.events"));
88+
}
89+
}
90+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
using System.IO;
3+
using System.Threading.Tasks;
4+
using Xunit;
5+
using Xunit.Abstractions;
6+
7+
namespace PuppeteerSharp.Tests.PageTests
8+
{
9+
[Collection("PuppeteerLoaderFixture collection")]
10+
public class GeoLocationTests : PuppeteerPageBaseTest
11+
{
12+
public GeoLocationTests(ITestOutputHelper output) : base(output)
13+
{
14+
}
15+
16+
[Fact]
17+
public async Task ShouldWork()
18+
{
19+
await Context.OverridePermissionsAsync(TestConstants.ServerUrl, new[] { OverridePermission.Geolocation });
20+
await Page.GoToAsync(TestConstants.EmptyPage);
21+
await Page.SetGeolocationAsync(new GeolocationOption
22+
{
23+
Longitude = 10,
24+
Latitude = 10
25+
});
26+
var geolocation = await Page.EvaluateFunctionAsync<GeolocationOption>(
27+
@"() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => {
28+
resolve({latitude: position.coords.latitude, longitude: position.coords.longitude});
29+
}))");
30+
Assert.Equal(new GeolocationOption
31+
{
32+
Latitude = 10,
33+
Longitude = 10
34+
}, geolocation);
35+
}
36+
37+
[Fact]
38+
public async Task ShouldThrowWhenInvalidLongitude()
39+
{
40+
var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
41+
Page.SetGeolocationAsync(new GeolocationOption
42+
{
43+
Longitude = 200,
44+
Latitude = 100
45+
}));
46+
Assert.Contains("Invalid longitude '200'", exception.Message);
47+
}
48+
}
49+
}

lib/PuppeteerSharp/Browser.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ public Browser(
6060
DefaultViewport = defaultViewport;
6161
TargetsMap = new Dictionary<string, Target>();
6262
ScreenshotTaskQueue = new TaskQueue();
63-
_defaultContext = new BrowserContext(this, null);
63+
DefaultContext = new BrowserContext(Connection, this, null);
6464
_contexts = contextIds.ToDictionary(keySelector: contextId => contextId,
65-
elementSelector: contextId => new BrowserContext(this, contextId));
65+
elementSelector: contextId => new BrowserContext(Connection, this, contextId));
6666

6767
Connection.Closed += (object sender, EventArgs e) => Disconnected?.Invoke(this, new EventArgs());
6868
Connection.MessageReceived += Connect_MessageReceived;
@@ -77,7 +77,6 @@ public Browser(
7777

7878
private readonly Dictionary<string, BrowserContext> _contexts;
7979
private readonly ILogger<Browser> _logger;
80-
private readonly BrowserContext _defaultContext;
8180
private readonly ChromiumProcess _chromiumProcess;
8281
private Task _closeTask;
8382

@@ -139,6 +138,12 @@ public Browser(
139138
/// </summary>
140139
public bool IsClosed => _closeTask != null && _closeTask.IsCompleted && _closeTask.Exception != null;
141140

141+
/// <summary>
142+
/// Returns the default browser context. The default browser context can not be closed.
143+
/// </summary>
144+
/// <value>The default context.</value>
145+
public BrowserContext DefaultContext { get; }
146+
142147
internal TaskQueue ScreenshotTaskQueue { get; set; }
143148
internal Connection Connection { get; }
144149
internal ViewPortOptions DefaultViewport { get; }
@@ -151,7 +156,7 @@ public Browser(
151156
/// Creates a new page
152157
/// </summary>
153158
/// <returns>Task which resolves to a new <see cref="Page"/> object</returns>
154-
public Task<Page> NewPageAsync() => _defaultContext.NewPageAsync();
159+
public Task<Page> NewPageAsync() => DefaultContext.NewPageAsync();
155160

156161
/// <summary>
157162
/// Returns An Array of all active targets
@@ -181,7 +186,7 @@ public Browser(
181186
public async Task<BrowserContext> CreateIncognitoBrowserContextAsync()
182187
{
183188
var response = await Connection.SendAsync<CreateBrowserContextResponse>("Target.createBrowserContext", new { });
184-
var context = new BrowserContext(this, response.BrowserContextId);
189+
var context = new BrowserContext(Connection, this, response.BrowserContextId);
185190
_contexts[response.BrowserContextId] = context;
186191
return context;
187192
}
@@ -193,7 +198,7 @@ public async Task<BrowserContext> CreateIncognitoBrowserContextAsync()
193198
public BrowserContext[] BrowserContexts()
194199
{
195200
var allContexts = new BrowserContext[_contexts.Count + 1];
196-
allContexts[0] = _defaultContext;
201+
allContexts[0] = DefaultContext;
197202
_contexts.Values.CopyTo(allContexts, 1);
198203
return allContexts;
199204
}
@@ -374,7 +379,7 @@ private async Task CreateTargetAsync(TargetCreatedResponse e)
374379

375380
if (!(browserContextId != null && _contexts.TryGetValue(browserContextId, out var context)))
376381
{
377-
context = _defaultContext;
382+
context = DefaultContext;
378383
}
379384

380385
var target = new Target(

lib/PuppeteerSharp/BrowserContext.cs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
34
using System.Threading.Tasks;
45

@@ -10,10 +11,12 @@ namespace PuppeteerSharp
1011
/// </summary>
1112
public class BrowserContext
1213
{
14+
private readonly IConnection _connection;
1315
private readonly string _id;
1416

15-
internal BrowserContext(Browser browser, string contextId)
17+
internal BrowserContext(IConnection connection, Browser browser, string contextId)
1618
{
19+
_connection = connection;
1720
Browser = browser;
1821
_id = contextId;
1922
}
@@ -83,6 +86,36 @@ public Task CloseAsync()
8386
return Browser.DisposeContextAsync(_id);
8487
}
8588

89+
/// <summary>
90+
/// Overrides the browser context permissions.
91+
/// </summary>
92+
/// <returns>The task.</returns>
93+
/// <param name="origin">The origin to grant permissions to, e.g. "https://example.com"</param>
94+
/// <param name="permissions">
95+
/// An array of permissions to grant. All permissions that are not listed here will be automatically denied.
96+
/// </param>
97+
/// <example>
98+
/// <![CDATA[
99+
/// var context = browser.DefaultBrowserContext;
100+
/// await context.OverridePermissionsAsync("https://html5demos.com", new List<string> {"geolocation"});
101+
/// ]]>
102+
/// </example>
103+
/// <seealso href="https://developer.mozilla.org/en-US/docs/Glossary/Origin"/>
104+
public Task OverridePermissionsAsync(string origin, IEnumerable<OverridePermission> permissions)
105+
=> _connection.SendAsync("Browser.grantPermissions", new
106+
{
107+
origin,
108+
browserContextId = _id,
109+
permissions
110+
});
111+
112+
/// <summary>
113+
/// Clears all permission overrides for the browser context.
114+
/// </summary>
115+
/// <returns>The task.</returns>
116+
public Task ClearPermissionOverridesAsync()
117+
=> _connection.SendAsync("Browser.resetPermissions", new { browserContextId = _id });
118+
86119
internal void OnTargetCreated(Browser browser, TargetChangedArgs args) => TargetCreated?.Invoke(browser, args);
87120

88121
internal void OnTargetDestroyed(Browser browser, TargetChangedArgs args) => TargetDestroyed?.Invoke(browser, args);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
using Newtonsoft.Json;
3+
4+
namespace PuppeteerSharp
5+
{
6+
/// <summary>
7+
/// Geolocation option.
8+
/// </summary>
9+
/// <seealso cref="Page.SetGeolocationAsync(GeolocationOption)"/>
10+
public class GeolocationOption : IEquatable<GeolocationOption>
11+
{
12+
/// <summary>
13+
/// Latitude between -90 and 90.
14+
/// </summary>
15+
/// <value>The latitude.</value>
16+
[JsonProperty("latitude")]
17+
public int Latitude { get; set; }
18+
/// <summary>
19+
/// Longitude between -180 and 180.
20+
/// </summary>
21+
/// <value>The longitude.</value>
22+
[JsonProperty("longitude")]
23+
public int Longitude { get; set; }
24+
/// <summary>
25+
/// Optional non-negative accuracy value.
26+
/// </summary>
27+
/// <value>The accuracy.</value>
28+
[JsonProperty("accuracy")]
29+
public int Accuracy { get; set; }
30+
31+
/// <summary>
32+
/// Determines whether the specified <see cref="PuppeteerSharp.GeolocationOption"/> is equal to the current <see cref="T:PuppeteerSharp.GeolocationOption"/>.
33+
/// </summary>
34+
/// <param name="other">The <see cref="PuppeteerSharp.GeolocationOption"/> to compare with the current <see cref="T:PuppeteerSharp.GeolocationOption"/>.</param>
35+
/// <returns><c>true</c> if the specified <see cref="PuppeteerSharp.GeolocationOption"/> is equal to the current
36+
/// <see cref="T:PuppeteerSharp.GeolocationOption"/>; otherwise, <c>false</c>.</returns>
37+
public bool Equals(GeolocationOption other)
38+
=> other != null &&
39+
Latitude == other.Latitude &&
40+
Longitude == other.Longitude &&
41+
Accuracy == other.Accuracy;
42+
/// <inheritdoc/>
43+
public override bool Equals(object obj) => Equals(obj as GeolocationOption);
44+
/// <inheritdoc/>
45+
public override int GetHashCode()
46+
=> (Latitude.GetHashCode() ^ 2014) +
47+
(Longitude.GetHashCode() ^ 2014) +
48+
(Accuracy.GetHashCode() ^ 2014);
49+
}
50+
}

0 commit comments

Comments
 (0)