Как записать скриншот если тест падает allure ?

screenshot
execution
allure
webdriver
python
Теги: #<Tag:0x00007fedbaf7b020> #<Tag:0x00007fedbaf7aee0> #<Tag:0x00007fedbaf7ada0> #<Tag:0x00007fedbaf7ac60> #<Tag:0x00007fedbaf7ab20>

(Вадим) #1

Использую Selenium + Allure + Python, подскажите как правильно выглядит запись для сохранения скриншотов если тест упал.


Прикрепление скриншотов через selene к allure-отчету
(Vladislav Kulasov) #2

У меня сделанно перехватом JUNIT TestWatchers

    @Rule
    public TestWatcher watchman = new TestWatcher() {
        String fileName;

        @Override
        protected void failed(Throwable e, Description description) {
            screenshot();
        }

    @Attachment(value = "Page screenshot", type = "image/png")
    public byte[] saveScreenshot(byte[] screenShot) {
        return screenShot;
    }

    public void screenshot() {
        if (driver == null) {
            log.info("Driver for screenshot not found");
            return;
        }

        saveScreenshot(((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES));
   
    }
}

(Лишний код убран)

PS: не заметил что питон, но принцип думаю тот же.


(Вадим) #3

В джаве не очень понимаю, все же подожду примера на пайтоне


(Пётр Алексеев) #4

Ну например как-то так

    with allure.step('Шаг 1'):
        try:
            browser.get('https://www.google.ru')
        except:
            allure.attach('error_screen', browser.get_screenshot_as_png(), type=AttachmentType.PNG)
            raise


(Вадим) #5

А можно его где-то в методе tearDown написать или же для каждого степа надо прописывать это условие ?


(Пётр Алексеев) #6

Ну попробую заюзать py.test. Там это точно возможно сделать

Что плохого в том, что ты пропишешь это условие для каждого теста? Как вариант еще наверно можно было бы намутить специальный декоратор

И еще не обязательно прописывать для каждого степа. Оберни все степы в один try:except


(rmerkushin) #7

Уже писал в нескольких темах, делается как-то так:

@pytest.mark.tryfirst
def pytest_runtest_makereport(item, call, __multicall__):
    rep = __multicall__.execute()
    setattr(item, "rep_" + rep.when, rep)
    return rep

@pytest.fixture(scope="function")
def screenshot_on_failure(request):
    def fin():
        driver = SeleniumWrapper().driver
        attach = driver.get_screenshot_as_png()
        if request.node.rep_setup.failed:
            allure.attach(request.function.__name__, attach, allure.attach_type.PNG)
        elif request.node.rep_setup.passed:
            if request.node.rep_call.failed:
                allure.attach(request.function.__name__, attach, allure.attach_type.PNG)
    request.addfinalizer(fin)

Все это кладется в conftest.py и тест где идут проверки с web-driver’ом маркируется фикстурой screenshot_on_failure

P.S.: Пользуйтесь поиском по форуму :slight_smile:


#8

Я у себя организовал это через хук pytest_exception_interact самого py.test.

def pytest_exception_interact(node, call, report):
    driver = node.instance.driver
    # ...
    allure.attach(
        name='Скриншот',
        contents=driver.get_screenshot_as_png(),
        type=allure.constants.AttachmentType.PNG,
    )
    # ...

Каждый раз при падении любого теста к нему будет подкреплен соответствующий скриншот.
Все хуки кладутся в conftest.py.


Прикрепление скриншотов через selene к allure-отчету
(rmerkushin) #9

А что если в тест сьюте где проверяется веб еще идет проверка БД например? Будет ли срабатывание хука на фейле БД теста?


#10

На сколько я знаю, хук pytest_exception_interact срабатывает всегда, когда в тесте возникает какое-либо исключение. Исключение состатвляет те случаи, когда тест обернут в @pytest.mark.xfail(raises=...).
Если исключение возникло в коде подготовки стенда, в фикстурах, например, в conftest.py можно определить другой хук - pytest_internalerror.


(rmerkushin) #11

Понял, но наверное можно навелосипедить чтобы он как то понимал что за тест.

P.S.: под работой с бд я подразумевал не прекондишн, а например такой тест: заполняете формочку создания юзера, жмякаете ок и смотрите что вылезло сообщение это один тест в сьюте, а второй например чекает что юзер в бд создался.


(Sergey Pirogov) #12

есть еще способо сделать через EvenFiringDriver


(rmerkushin) #13

Интересный способ. Спасибо за наводку )


(Вадим) #14

Добрый день, пробую использовать эту фикстуру, получаю ошибку > “TypeError: Object of type ‘bytes’ is not JSON serializable” и вот такое :

self = <allure_pytest.listener.AllureListener object at 0x10f529438>
fixturedef = <FixtureDef name='screenshot_on_failure' scope='function' baseid='simple.py' >

    @pytest.hookimpl(hookwrapper=True)
    def pytest_fixture_post_finalizer(self, fixturedef):
        yield
        if hasattr(fixturedef, 'cached_result') and self._cache.get(fixturedef):
            container_uuid = self._cache.pop(fixturedef)
>           self.allure_logger.stop_group(container_uuid, stop=now())

/usr/local/lib/python3.6/site-packages/allure_pytest/listener.py:140: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.6/site-packages/allure_commons/reporter.py:43: in stop_group
    plugin_manager.hook.report_container(container=group)
/usr/local/lib/python3.6/site-packages/allure_commons/logger.py:38: in report_container
    self._report_item(container)
/usr/local/lib/python3.6/site-packages/allure_commons/logger.py:30: in _report_item
    json.dump(data, json_file, indent=indent, ensure_ascii=False)
/usr/local/Cellar/python/3.6.4_4/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/__init__.py:179: in dump
    for chunk in iterable:
/usr/local/Cellar/python/3.6.4_4/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py:430: in _iterencode
    yield from _iterencode_dict(o, _current_indent_level)
/usr/local/Cellar/python/3.6.4_4/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py:404: in _iterencode_dict
    yield from chunks
/usr/local/Cellar/python/3.6.4_4/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py:325: in _iterencode_list
    yield from chunks
/usr/local/Cellar/python/3.6.4_4/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py:404: in _iterencode_dict
    yield from chunks
/usr/local/Cellar/python/3.6.4_4/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py:325: in _iterencode_list
    yield from chunks
/usr/local/Cellar/python/3.6.4_4/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py:404: in _iterencode_dict
    yield from chunks
/usr/local/Cellar/python/3.6.4_4/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py:437: in _iterencode
    o = _default(o)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <json.encoder.JSONEncoder object at 0x10ffdca90>
o = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x04\xb0\x00\x00\x02\xcd\x08\x06\x00\x00\x00?M\x98\xea\x00\x00 \x00IDATx\...1\x90 \x08\x82\x18\xfbTUU\xe2\xbbK\xbe\x83D"\xa1\xdeG\x08\x820\xf9?\rg{\x93\x8b\xbc\x98r\x00\x00\x00\x00IEND\xaeB`\x82'

    def default(self, o):
        """Implement this method in a subclass such that it returns
            a serializable object for ``o``, or calls the base implementation
            (to raise a ``TypeError``).
    
            For example, to support arbitrary iterators, you could
            implement default like this::
    
                def default(self, o):
                    try:
                        iterable = iter(o)
                    except TypeError:
                        pass
                    else:
                        return list(iterable)
                    # Let the base class default method raise the TypeError
                    return JSONEncoder.default(self, o)
    
            """
        raise TypeError("Object of type '%s' is not JSON serializable" %
>                       o.__class__.__name__)
E       TypeError: Object of type 'bytes' is not JSON serializable

/usr/local/Cellar/python/3.6.4_4/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py:180: TypeError

Подскажите, почему такое получилось ? Спасибо


(rmerkushin) #15

Скорее всего у вас питон 3х и у вас строки в байтах. b.decode('utf_8') вроде так


(Вадим) #16

Подскажите, куда эту строку добавить ?