is it possible to look for elements only within the "At-Element" ?
Example Page:
class SearchDialogPage extends Page {
static at = { $('div', class: 'modalOverlay').has('div', class: 'contentbox__title', text: 'Search for Company') }
static content = {
nameTextline { $('div').has('label', text:'Name').$('input') module TextInput }
}
} }
I find more than one element for nameTextline, so i want to tell the Page, that it has to look into the div-Element declared in the "at" field.
Semantically, at is not a content element but a boolean condition, i.e. whatever code you have inside there will be evaluated as a "Groovy truthy" value. You should define your element in the content section, then refer to it from your at condition, not the other way around.
Related
I have a simple LitElement component like so:
class MyElement extends LitElement {
constructor() {
super();
}
customMethod(data) {
// do something with the passed parameter
}
render() {
return html`<div id="element"></div>`;
}
}
customElements.define('my-element', MyElement);
And I want to be able to call that customMethod from outside of my element.
So for example if I add the element to web page like so:
<my-element></my-element>
I then want to be able to add some JavaScript to the page and call that customMethod.
I tried:
var element = document.getElementById('element');
element.shadowRoot.customMethod('example data');
But it claims it's not available... How can I call a method on an instance of LitElement?
You don't need to use shadowRoot in the call :
var element = document.getElementById('element');
element.customMethod('example data');
but you need to be able to locate your element
<my-element id='element'></my-element>
I had a very similar problem and the existing answers did not seem to fix it. The reason for my issue was caused by the fact that LIT Element scripts are exported as modules, meaning that they are loaded and executed after the initial DOM has been parsed. So if you are using a script to access the public method - make sure that it is also in a module (or you can alternatively place the code into an appropriate timeout).
So when defining an element in LIT Element as follows:
#customElement('my-element')
export class MyElement extends LitElement {
#state()
text = '';
customMethod(data) {
this.text = 'Custom method was called!';
}
render() {
return html`<div id="element">${this.text}</div>`;
}
}
And adding a script in my index.html page:
<my-element id='element'></my-element>
<script type="module">
const element = document.getElementById('element');
element.customMethod();
</script>
Make sure that the script tag contains type="module". Otherwise you will see the following error in the console: Uncaught TypeError: element.customMethod is not a function
Link to LIT Element Playground.
Also, here is a great article that explains how scripts are loaded in detail.
We use Geb to run our Frontend Tests and we have some quite complex pages in our application.
Some of the pages have forms with a lot of different buttons, checkboxes and some multiselects.
I love the feature of geb/groovy that i just have to define the form in the Page Object and then can access all its elements in it.
static content = {
form { $("#form")}
}
But for them to be clickable and to query if they are readonly and more they need to be at least of type FormElement which does not happen with the above method. So I have to mention all these FormElements separately:
static content = {
form { $("#form")}
button1 { $("#button1").module(FormElement)}
button2 { $("#button2").module(FormElement)}
checkbox{ $("#checkbox").module(Checkbox)}
...
}
All those buttons, checkboxes... are already in the form variable, but cannot be clicked or checked if they are selected and so on. It's also not possible to apply the the module afterwards like this:
def "test something"() {
when:
form.button1.module(FormElement).click() //error
then:
...
}
Is there no way to automatically assign each input, checkbox, radiobutton, button,... the correct Module based on their type without the need of doing it by hand?
If someone could also point me in the right direction to understand how this "form { $("#form")}" works, that i can access all sub elements by its name by just suppying the form, that would be nice!
For your example of creating a module based on a form control you need to obtain a navigator for the control and not it's value. It's done by calling a method named the same as the control you're trying to access (it's explained in this section of The Book of Geb):
form.button1().module(FormElement).click()
If you want to automatically create modules based on the element type then you could create a Module for the form and override method missing:
class FormModule extends Module {
Object methodMissing(String name, Object args) {
def result = super.methodMissing(name, args)
if (result instanceof Navigator && result.tag() == "input") {
switch (result.#type) {
case "checkbox":
result = result.module(Checkbox)
break
default:
result = result.module(FormElement)
}
}
result
}
}
then you would use it like:
static content = {
form { $("#form").module(FormModule) }
}
form.button1().click()
I'm new to Groovy and testing a website with reasonably complex workflows using Geb/Cucumber.
I have a set of product pages represented by page classes Prod1Page, Prod2Page and Prod3Page. They share certain characteristics that can be tested by the same code.
I want to write a ProductPage page class along the lines of:
class ProductPage extends Page {
...
static at = {...}
...
}
so that
Given(~/.../) {
...
at ProductPage
...
}
would pass if any of the following were true:
at Prod1Page
at Prod2Page
at Prod3Page
In case it's not clear my goal is to avoid duplicating the details of Prod[1-3]Page classes somewhere else.
Thanks for any insights,
Nick
In the ProductPage's at checker just assert that the common HTML elements between all Prod#Pages are available. For example, if all ProdPages have a <div id="productContainer"> element then you can put the following in your ProductPage class:
class ProductPage extends Page{
static at = { $("#productContainer").displayed }
...
}
Now in your test you can call at ProductPage and it will assert true if that common element is currently available on the WebDriver's DOM; and you will know that you are on one of the ProdPages.
As I understand it, a QML Component is like a kind of like a class in C++. It contains the definition of a QML object but isn't an instance of it. You can create a Component in these ways:
Creating a .qml file with the component name as its filename.
Define it inline with the Component { } syntax.
However these are actually two different things. The second one is more like a factory because you can do things like:
Component {
id: factory
Rectangle { width: 100; height:100; color: "red }
}
Component.onCompleted: {
var rect1 = factory.createObject(parent);
}
Whereas with the separate file you need to first load it into a factory like this:
var factory = Qt.createComponent("RedRectangle.qml")
var rect1 = factory.createObject(parent);
I'm only concerned with dynamic object creation, so this is not an option:
RedRectangle {
id: rect1
}
My question is: is there a way to create the objects dynamically, without having to create the Component factory dynamically too, and without having to specify the Component inline. I.e. I want the first example, but where the Rectangle is specified in another file.
I want this:
Component {
id: factory
url: "RedRectangle.qml"
}
Component.onCompleted: {
var rect1 = factory.createObject(parent);
}
Sadly that doesn't work. I also tried this:
Component {
id: factory
}
Component.onCompleted: factory.loadUrl("RedRectangle.qml");
But it doesn't work either. Am I being stupid or is this just not supported?
Here is some encapsulation:
Fact.qml (for some reason it doesn't let me name it Factory)
QtObject {
property string url
readonly property Component component : Qt.createComponent(url)
function get() { return component }
function load(url) { return Qt.createComponent(url) }
}
usage:
Fact {
id: f
url: "RedRect.qml"
}
StackView {
id: stack
}
Component.onCompleted: {
stack.push(f.component) // redrect
f.url = "BlueRect.qml"
stack.push(f.get()) // bluerect, redundant but shorter
stack.push(f.load("GreenRect.qml")) // greenrect, f.component is still bluerect
}
It will only load the component when its component property is referenced and you can change the url to load other components with the same Fact instance. Also the auxiliary load() method, which returns a component without actually changing the one potentially cached.
Actually the answer is not too bad, though I still think Component should support specifying a url directly.
Here is my solution:
property var factory: Qt.createComponent("RedRectangle.qml")
When I click a button I have to wait for some dynamic content to be rendered. When I put the waitFor closure in the test it works correctly. However, I wanted to put the waitFor in a method inside the Page object so I do not have to always call the waitFor after every click, but when I do that it fails stating it cannot find the property.
This does not work:
class LandingPage extends Page {
static content = {
resultsBtn(to: ResultsPage) { $("button", id: "showresults") }
}
void getResults() {
resultsBtn.click()
waitFor { ResultsPage.results.displayed }
}
}
class ResultsPage extends Page {
static content = {
results { $("div", id: "listresults") }
}
}
class ShowResults extends GebReportingTest {
#Test
public void displayResults() {
to LandingPage
getResults()
}
}
The error states something like "No such property: results for class ResultsPage".
Is it possible to put references to content from other Page Objects inside other Page Object methods?
EDIT: I feel like this is more of a Groovy specific thing rather than Geb. I'm not sure if it's even possible to access bindings within the content closure. But it also seems like creating a getVariable() function inside the Page Object doesn't help much either.
First you shouldn't assign closures in content blocks (there's unnecessary = in ResultPage) but pass them to an implicit method, you should have:
static content = {
results { $("div", id: "listresults") }
}
The other question is why do you want to model this as two pages? As far as I understand clicking the button doesn't cause a page reload but there's an ajax call to retrieve the results. I would simply put both results and resultsBtn as contents of one page and your problem would be gone.
EDIT:
It turns out that a page change is involved in your case. Assuming that you always want to wait for these results to appear you can either:
put your waitFor condition inside of static at = {} block for ResultsPage - at checks are executed implicitly whenever you use to() which means that it will wait wherever you go to that page
put a waitFor in a page change listener
access current page via the browser property on a page, in LandingPage: waitFor { browser.page.results.displayed } but this seems like a dirty solution to me - reaching from one page to another...