Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add screenshot teardown fixture - approach 1 #421

Merged
merged 7 commits into from
Jan 9, 2023
Merged

Conversation

danielhollas
Copy link
Contributor

@danielhollas danielhollas commented Dec 16, 2022

We want to take a screenshot at the end of each selenium test, even if (and especially) when the test fails.
So essentially we need to take the screenshot as part of the test teardown. Here's the relevant section of the pytest docs

https://docs.pytest.org/en/6.2.x/fixture.html#teardown-cleanup-aka-fixture-finalization

Our case is complicated by the fact that we somehow need to pass the screenshot name to the teardown method.
Here's the relevant section in docs and two SO questions were helpful:

https://docs.pytest.org/en/6.2.x/fixture.html#fixtures-can-introspect-the-requesting-test-context
https://stackoverflow.com/questions/58549670/pass-a-parameter-to-a-pytest-fixture-for-teardown

https://stackoverflow.com/questions/38488571/attributeerror-when-using-request-function-in-pytest-yield-fixture

In this PR, we take the approach of having a separate yield fixture. We pass the screenshot filename as a parameter to a dictionary object that is yielded by the fixture. Still feels a bit hacky, but it is quite clean and compact.

I'll also submit an PR with an alternative solution, where we extend the existing selenium_driver fixture.

Closes #419

Copy link
Member

@unkcpz unkcpz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks a lot! @danielhollas
This is very interesting, and I prefer this approach as you said having a dedicated fixture is more clear and easy to maintain in the future.

But is that possible to make this fixture call as a function by screenshot("process-list.png")?
If I want to take screenshots at different stages of the test, how can I do it with this fixture?

driver = selenium_driver("notebooks/process.ipynb")
screenshot["name"] = "process.png"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the fixture must be called at the very beginning or it can be moved to the last which makes more sense?

Copy link
Member

@yakutovicha yakutovicha Jan 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think running this at the beginning is better. This line sets the screenshot filename and puts on hold the rest of the code that follows yield:

yield ctx
if not ctx["name"]:
raise ValueError("Empty the screenshot filename") # noqa: TC003
selenium.get_screenshot_as_file(f"{screenshot_dir}/{ctx['name']}")

The code after yield will be executed once all the tests are completed. More information can be found following the link shared by @danielhollas.

@danielhollas
Copy link
Contributor Author

But is that possible to make this fixture call as a function by screenshot("process-list.png")?
If I want to take screenshots at different stages of the test, how can I do it with this fixture?

The whole purpose of this fixture is to be called at the end of the test, regardless of whether test succeeded or not. So if you need to take screenshot somewhere in the middle, you should still use selenium driver directly as before. Does that make sense?

@unkcpz
Copy link
Member

unkcpz commented Jan 5, 2023

So if you need to take screenshot somewhere in the middle, you should still use selenium driver directly as before. Does that make sense?

It makes sense. That also means with this fixture it is expected to have only one screenshot for a test case. I'd then suggest setting the filename of the screenshot based on the function name, and then the fixture called inside the test function screenshot["name"] = "<test_function_name>.png" can be removed. Is that possible to implement?

@danielhollas
Copy link
Contributor Author

danielhollas commented Jan 5, 2023 via email

Copy link
Member

@yakutovicha yakutovicha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @danielhollas. I also prefer this approach w.r.t. #422.

I have just a few suggestions.

tests/test_notebooks.py Outdated Show resolved Hide resolved
tests/conftest.py Outdated Show resolved Hide resolved
@danielhollas
Copy link
Contributor Author

@yakutovicha @unkcpz thanks for the reviews! I've modified the new fixture so that the screenshot name is determined automatically from the test function name. For example, this test

def test_something_important(final_screenshot):
       pass

will automatically generate a screenshot titled something_important.png.

I've also changed the fixture name from screenshot to final_screenshot to make it clear that this is a teardown fixture.

Copy link
Member

@unkcpz unkcpz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danielhollas thanks! This is very good now. Just have one minor request. I think it is better to add a dummy test that will fail and no screenshot taken but not stop other tests to show it works as expected.

Screenshot name is generated from the test function name
by stripping the 'test_' prefix
"""
screenshot_name = f"{request.function.__name__[5:]}.png"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, using request.function, didn't know that. This is brilliant!

Screenshot name is generated from the test function name
by stripping the 'test_' prefix
"""
screenshot_name = f"{request.function.__name__[5:]}.png"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
screenshot_name = f"{request.function.__name__[5:]}.png"
screenshot_name = f"{request.function.__name__.lstrip('test_')}.png"

Maybe this is more pointy to the purpose?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, then just ignore my request.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that in Python 3.9 one could use the removeprefix string method for exactly this, but we'd need to drop support for Python 3.8, at least in tests. Probably not worth it now.

unkcpz
unkcpz previously approved these changes Jan 9, 2023
Copy link
Member

@unkcpz unkcpz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. @danielhollas I'll let you merge and polish the commit message.

yakutovicha
yakutovicha previously approved these changes Jan 9, 2023
@danielhollas
Copy link
Contributor Author

I think it is better to add a dummy test that will fail and no screenshot taken but not stop other tests to show it works as expected.

In the last commit I added an intentionally failing test to verify that the fixture works as intended. However, I don't think having this failing test around adds value and it is confusing since it is not testing the application in any way. In the pytest output it now says

============ 10 passed, 1 xfailed, 20 warnings in 183.90s (0:03:03) ============

which again to me rather adds to the confusion.

Leaving up to you. If you want to remove it feel free to revert the last commit.

Looks good to me. @danielhollas I'll let you merge and polish the commit message.

I don't have privileges to merge.

obrazek

@unkcpz unkcpz enabled auto-merge (squash) January 9, 2023 13:42
@unkcpz unkcpz merged commit da5f992 into master Jan 9, 2023
@unkcpz unkcpz deleted the screenshot-fixture-1 branch January 9, 2023 13:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

When tests fail, the images are not uploaded as artifacts
3 participants