Skip to content

Commit

Permalink
NEW: query._frame_context as search context
Browse files Browse the repository at this point in the history
  • Loading branch information
yashaka committed May 18, 2024
1 parent d415faf commit e6fcf16
Show file tree
Hide file tree
Showing 7 changed files with 479 additions and 29 deletions.
19 changes: 14 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,25 @@ TODOs:

## 2.0.0rc10 (to be released on DD.05.2024)

### A context manager and decorator to work with iFrames (Experimental)
### A context manager, decorator and search context to work with iFrames (Experimental)

```python
from selene import browser, query
from selene import browser, query, have

my_frame_context = browser.element('#my-iframe').get(query._frame_context)
# now simply:
my_frame_context._element('#inside-iframe').click()
my_frame_context._all('.items-inside-iframe').should(have.size(3))
# – here switching to frame and back happens for each command implicitly
...
# or
with my_frame_context:
# here elements inside frame will be found
# and you can work with them;)
# here elements inside frame will be found when searching via browser
browser.element('#inside-iframe').click()
browser.all('.items-inside-iframe').should(have.size(3))
# this is the most speedy version,
# because switching to frame happens on entering the context
# and switching back to default content happens on exiting the context
...

@my_frame_context._within
Expand All @@ -122,7 +131,7 @@ def do_something(self):
do_something()
...

# Switch to default content happens automatically in both cases;)
# Switch to default content happens automatically, nevertheless;)
```

See a bit more in documented ["FAQ: How to work with iFrames in Selene?"](https://yashaka.github.io/selene/faq/iframes-howto/) and much more in ["Reference: `query.*`](https://yashaka.github.io/selene/reference/query).
Expand Down
160 changes: 153 additions & 7 deletions docs/faq/iframes-howto.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ iframe = browser.element('#editor-iframe')
iframe_webelement = iframe.locate()
# THEN
browser.driver.switch_to.frame(iframe_webelement)
# AND work with textarea inside frame:
# AND work with elements inside frame:
browser.all('strong').should(have.size(0))
browser.element('.textarea').type('Hello, World!').perform(command.select_all)
# AND switch back...
browser.driver.switch_to.default_content()
Expand All @@ -22,14 +23,140 @@ browser.element('#toolbar').element('#bold').click()
# AND come back to ...
browser.driver.switch_to.frame(iframe_webelement)
# AND ...
browser.all('strong').should(have.size(1))
browser.element('.textarea').should(
have.js_property('innerHTML').value(
'<p><strong>Hello, world!</strong></p>'
)
)
```

In addition to that, Selene provides an experimental feature – [`query._frame_context`][selene.core.query._frame_context], that removes a bit of boilerplate:
In addition to that...

## Selene provides an experimental feature – [query._frame_context][selene.core.query._frame_context] that...

### 1. Either removes a lot of boilerplate but might result in performance drawback

=== "as frame search context (formatted)"

```python
from selene import browser, command, have, query


...
# GIVEN
iframe = browser.element('#editor-iframe').get(query._frame_context)




# THEN work with elements as if iframe is a normal parent element
iframe._all('strong').should(have.size(0))
iframe._element('.textarea').type('Hello, World!').perform(command.select_all)


# AND still dealing with elements outside iframe as usual
browser.element('#toolbar').element('#bold').click()


# AND ...
iframe._all('strong').should(have.size(1))
iframe._element('.textarea').should(
have.js_property('innerHTML').value(
'<p><strong>Hello, world!</strong></p>'
)
)
```

=== "➡️ removed comments"

```python
from selene import browser, command, have, query


...

iframe = browser.element('#editor-iframe').get(query._frame_context)
iframe._all('strong').should(have.size(0))
iframe._element('.textarea').type('Hello, World!').perform(command.select_all)
browser.element('#toolbar').element('#bold').click()
iframe._all('strong').should(have.size(1))
iframe._element('.textarea').should(
have.js_property('innerHTML').value(
'<p><strong>Hello, world!</strong></p>'
)
)
```

=== "➡️ formatted"

```python
from selene import browser, command, have, query


...

iframe = browser.element('#editor-iframe').get(query._frame_context)





iframe._all('strong').should(have.size(0))
iframe._element('.textarea').type('Hello, World!').perform(command.select_all)



browser.element('#toolbar').element('#bold').click()



iframe._all('strong').should(have.size(1))
iframe._element('.textarea').should(
have.js_property('innerHTML').value(
'<p><strong>Hello, world!</strong></p>'
)
)
```

=== "driver.switch_to.*"

```python
from selene import browser, command, have


...

iframe = browser.element('#editor-iframe')

iframe_webelement = iframe.locate()

browser.driver.switch_to.frame(iframe_webelement)

browser.all('strong').should(have.size(0))
browser.element('.textarea').type('Hello, World!').perform(command.select_all)

browser.driver.switch_to.default_content()

browser.element('#toolbar').element('#bold').click()

browser.driver.switch_to.frame(iframe_webelement)

browser.all('strong').should(have.size(1))
browser.element('.textarea').should(
have.js_property('innerHTML').value(
'<p><strong>Hello, world!</strong></p>'
)
)
```

!!! note

Some examples above and below are formatted and aligned for easier comparison of the corresponding parts of code. The additional new lines are added so you can directly see the differences between the examples.

The performance may decrease because Selene under the hood has to switch to the frame context and back for each element action.

### 2. Or removes a bit of boilerplate keeping the performance most optimal

=== "with frame context"

Expand All @@ -44,7 +171,8 @@ In addition to that, Selene provides an experimental feature – [`query._frame_

# THEN
with iframe.get(query._frame_context):
# AND work with textarea inside frame:
# AND work with elements inside frame:
browser.all('strong').should(have.size(0))
browser.element('.textarea').type('Hello, World!').perform(command.select_all)
# AND switch back AUTOMATICALLY...

Expand All @@ -53,6 +181,7 @@ In addition to that, Selene provides an experimental feature – [`query._frame_
# AND come back to ...
with iframe.get(query._frame_context):
# AND ...
browser.all('strong').should(have.size(1))
browser.element('.textarea').should(
have.js_property('innerHTML').value(
'<p><strong>Hello, world!</strong></p>'
Expand All @@ -74,6 +203,7 @@ In addition to that, Selene provides an experimental feature – [`query._frame_

with iframe.get(query._frame_context):

browser.all('strong').should(have.size(0))
browser.element('.textarea').type('Hello, World!').perform(command.select_all)


Expand All @@ -82,6 +212,7 @@ In addition to that, Selene provides an experimental feature – [`query._frame_

with iframe.get(query._frame_context):

browser.all('strong').should(have.size(1))
browser.element('.textarea').should(
have.js_property('innerHTML').value(
'<p><strong>Hello, world!</strong></p>'
Expand All @@ -103,6 +234,7 @@ In addition to that, Selene provides an experimental feature – [`query._frame_

browser.driver.switch_to.frame(iframe_webelement)

browser.all('strong').should(have.size(0))
browser.element('.textarea').type('Hello, World!').perform(command.select_all)

browser.driver.switch_to.default_content()
Expand All @@ -111,18 +243,17 @@ In addition to that, Selene provides an experimental feature – [`query._frame_

browser.driver.switch_to.frame(iframe_webelement)

browser.all('strong').should(have.size(1))
browser.element('.textarea').should(
have.js_property('innerHTML').value(
'<p><strong>Hello, world!</strong></p>'
)
)
```

!!! note
The performance is kept optimal because via `with` statement we can group actions – then switching to the frame will happen only once before group and once after, not wasting time to re-switch for each action in the group.

Some examples above and below are formatted and aligned for easier comparison of the corresponding parts of code. The additional new lines are added so you can directly see the differences between the examples.

It also has a handy [`_within`][selene.core.query._frame_context._within] decorator to tune PageObject steps to work with iframes:
### 3. It also has a handy [_within][selene.core.query._frame_context._within] decorator to tune PageObject steps to work with iframes

=== "_within decorator"

Expand All @@ -141,6 +272,11 @@ It also has a handy [`_within`][selene.core.query._frame_context._within] decora
self.text_area.type(text)
return self

@area_frame._within
def should_have_bold_text_parts(self, count):
self.text_area.all('strong').should(have.size(count))
return self

@area_frame._within
def select_all(self):
self.text_area.perform(command.select_all)
Expand Down Expand Up @@ -170,8 +306,10 @@ It also has a handy [`_within`][selene.core.query._frame_context._within] decora

editor = Editor()

editor.should_have_bold_text_parts(0)
editor.type('Hello, World!').select_all().set_bold()

editor.should_have_bold_text_parts(1)
editor.should_have_content_html(
'<p><strong>Hello, world!</strong></p>'
)
Expand All @@ -191,13 +329,15 @@ It also has a handy [`_within`][selene.core.query._frame_context._within] decora



editor.should_have_bold_text_parts(0)
editor.type('Hello, World!').select_all().set_bold()






editor.should_have_bold_text_parts(1)
editor.should_have_content_html(

'<p><strong>Hello, world!</strong></p>'
Expand All @@ -219,18 +359,24 @@ It also has a handy [`_within`][selene.core.query._frame_context._within] decora

with iframe.get(query._frame_context):

browser.all('strong').should(have.size(0))
browser.element('.textarea').type('Hello, World!').perform(command.select_all)


browser.element('#toolbar').element('#bold').click()

with iframe.get(query._frame_context):

browser.all('strong').should(have.size(1))
browser.element('.textarea').should(
have.js_property('innerHTML').value(
'<p><strong>Hello, world!</strong></p>'
)
)
```

!!! warning

Take into account, that because we break previously groupped actions into separate methods, the performance might decrease same way as it was with a "search context" style, as we have to switch to the frame context and back for each method call.

See a more detailed explanation and examples on [the feature reference][selene.core.query._frame_context].
Loading

0 comments on commit e6fcf16

Please sign in to comment.