blob: 9c8d9c87363ebafbd6b79b82ec64ed7792a6f26f [file] [log] [blame]
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import 'dart:math' show Point, Rectangle;
import 'package:webdriver/src/common/request_client.dart';
import 'package:webdriver/src/common/webdriver_handler.dart';
import 'package:webdriver/src/common/web_element.dart' as common;
import '../../async_core.dart' as async_core;
import '../common/by.dart';
import 'common.dart';
import 'web_driver.dart';
// ignore: uri_does_not_exist
import 'common_stub.dart'
// ignore: uri_does_not_exist
if (dart.library.io) 'common_io.dart';
/// WebDriver representation and interactions with an HTML element.
class WebElement extends common.WebElement implements SearchContext {
@override
final String id;
/// Produces a compatible [async_core.WebElement]. Allows backwards
/// compatibility with other frameworks.
async_core.WebElement get asyncElement => createAsyncWebElement(this);
@override
async_core.SearchContext get asyncContext => asyncElement;
/// The context from which this element was found.
final SearchContext? context;
@override
final WebDriver driver;
final SyncRequestClient _client;
final WebDriverHandler _handler;
/// How the element was located from the context.
final dynamic /* String | Finder */ locator;
/// The index of this element in the set of element founds. If the method
/// used to find this element always returns one element, then this is null.
final int? index;
WebElement(this.driver, this._client, this._handler, this.id,
[this.context, this.locator, this.index]);
WebElement get parent => WebElement(
driver,
_client,
_handler,
_client.send(
_handler.element.buildPropertyRequest(id, 'parentElement'),
_handler.elementFinder.parseFindElementResponse,
),
);
static final _parentCache = <String, String>{};
/// Gets a chain of parent elements, including the element itself.
List<String> get parents {
WebElement p = this;
final result = <String>[];
while (p.id != null) {
var id = p.id;
if (_parentCache.containsKey(id)) {
break;
}
result.add(id);
_parentCache[id] = (p = p.parent).id;
}
if (p.id != null) {
// Hit cache in the previous loop.
String? id = p.id;
while (id != null) {
result.add(id);
id = _parentCache[id];
}
}
return result;
}
/// Click on this element.
void click() {
_client.send(_handler.element.buildClickRequest(id),
_handler.element.parseClickResponse);
}
/// Send [keysToSend] to this element.
void sendKeys(String keysToSend) {
_client.send(_handler.element.buildSendKeysRequest(id, keysToSend),
_handler.element.parseSendKeysResponse);
}
/// Clear the content of a text element.
void clear() {
_client.send(_handler.element.buildClearRequest(id),
_handler.element.parseClearResponse);
}
/// Is this radio button/checkbox selected?
bool get selected => _client.send(_handler.element.buildSelectedRequest(id),
_handler.element.parseSelectedResponse);
/// Is this form element enabled?
bool get enabled => _client.send(_handler.element.buildEnabledRequest(id),
_handler.element.parseEnabledResponse);
/// Is this element visible in the page?
bool get displayed => _client.send(_handler.element.buildDisplayedRequest(id),
_handler.element.parseDisplayedResponse);
/// The location of the element.
///
/// This is assumed to be the upper left corner of the element, but its
/// implementation is not well defined in the JSON spec.
Point<int> get location => _client.send(
_handler.element.buildLocationRequest(id),
_handler.element.parseLocationResponse);
/// The size of this element.
Rectangle<int> get size => _client.send(_handler.element.buildSizeRequest(id),
_handler.element.parseSizeResponse);
/// The bounds of this element.
Rectangle<int> get rect {
final location = this.location;
final size = this.size;
return Rectangle<int>(location.x, location.y, size.width, size.height);
}
/// The tag name for this element.
String get name => _client.send(_handler.element.buildNameRequest(id),
_handler.element.parseNameResponse);
/// Visible text within this element.
String get text => _client.send(_handler.element.buildTextRequest(id),
_handler.element.parseTextResponse);
///Find an element nested within this element.
///
/// Throws [NoSuchElementException] if matching element is not found.
@override
WebElement findElement(By by) => WebElement(
driver,
_client,
_handler,
_client.send(_handler.elementFinder.buildFindElementRequest(by, id),
_handler.elementFinder.parseFindElementResponse),
this,
by);
/// Find multiple elements nested within this element.
@override
List<WebElement> findElements(By by) {
final ids = _client.send(
_handler.elementFinder.buildFindElementsRequest(by, id),
_handler.elementFinder.parseFindElementsResponse);
final elements = <WebElement>[];
int i = 0;
for (final id in ids) {
elements.add(WebElement(driver, _client, _handler, id, this, by, i++));
}
return elements;
}
/// Access to the HTML attributes of this tag.
Attributes get attributes => Attributes((name) => _client.send(
_handler.element.buildAttributeRequest(id, name),
_handler.element.parseAttributeResponse));
/// Access to the HTML properties of this tag.
Attributes get properties => Attributes((name) => _client.send(
_handler.element.buildPropertyRequest(id, name),
_handler.element.parsePropertyResponse));
/// Access to the cssProperties of this element.
Attributes get cssProperties => Attributes((name) => _client.send(
_handler.element.buildCssPropertyRequest(id, name),
_handler.element.parseCssPropertyResponse));
/// Are these two elements the same underlying element in the DOM.
bool equals(WebElement other) =>
other is WebElement && other.driver == this.driver && other.id == this.id;
@override
int get hashCode => driver.hashCode * 3 + id.hashCode;
@override
bool operator ==(other) =>
other is WebElement && other.driver == this.driver && other.id == this.id;
@override
String toString() {
final out = StringBuffer()..write(context);
if (locator is By) {
if (index == null) {
out.write('.findElement(');
} else {
out.write('.findElements(');
}
out..write(locator)..write(')');
} else {
out..write('.')..write(locator);
}
if (index != null) {
out..write('[')..write(index)..write(']');
}
return out.toString();
}
String toStringDeep() => "<$name>\n\nHTML:\n${properties['outerHTML']}";
}