Получение unittest Python приводит к разрушению () метод

Действительно ли возможно получить результаты теста (т.е. передали ли все утверждения) в разрушении () метод? Я запускаю скрипты Селена, и я хотел бы сделать некоторое создание отчетов из разрушения (), однако я не знаю, возможно ли это.

62
задан 11 December 2010 в 02:37

7 ответов

Вот решение для тех из нас, кто неудобные решения для использования, которые полагаются unittest внутренности:

Первый, мы создаем декоратора, который установит флаг на TestCase экземпляр, чтобы определить, привел ли тестовый сценарий к сбою или передал:

import unittest
import functools

def _tag_error(func):
    """Decorates a unittest test function to add failure information to the TestCase."""

    @functools.wraps(func)
    def decorator(self, *args, **kwargs):
        """Add failure information to `self` when `func` raises an exception."""
        self.test_failed = False
        try:
            func(self, *args, **kwargs)
        except unittest.SkipTest:
            raise
        except Exception:  # pylint: disable=broad-except
            self.test_failed = True
            raise  # re-raise the error with the original traceback.

    return decorator

Этот декоратор на самом деле довольно прост. Это полагается на то, которое unittest обнаруживает проваленные тесты через [1 111] Исключения . Насколько я знаю, только [1 112] особенные исключение, которое должно быть обработано, unittest.SkipTest (который не указывает на отказ при испытании). Все другие исключения указывают на отказы при испытании, таким образом, мы отмечаем их как таковой, когда они пузырятся до нас.

Мы можем теперь использовать этого декоратора непосредственно:

class MyTest(unittest.TestCase):
    test_failed = False

    def tearDown(self):
        super(MyTest, self).tearDown()
        print(self.test_failed)

    @_tag_error
    def test_something(self):
        self.fail('Bummer')
<час>

Это собирается получить действительно раздражающую запись этого декоратора все время. Существует ли способ, которым мы можем упростить? Да существует! <глоток> * Мы можем записать метакласс для обработки применения декоратора для нас:

class _TestFailedMeta(type):
    """Metaclass to decorate test methods to append error information to the TestCase instance."""
    def __new__(cls, name, bases, dct):
        for name, prop in dct.items():
            # assume that TestLoader.testMethodPrefix hasn't been messed with -- otherwise, we're hosed.
            if name.startswith('test') and callable(prop):
                dct[name] = _tag_error(prop)

        return super(_TestFailedMeta, cls).__new__(cls, name, bases, dct)

Теперь мы применяем это к нашей базе TestCase подкласс, и мы все установлены:

import six  # For python2.x/3.x compatibility

class BaseTestCase(six.with_metaclass(_TestFailedMeta, unittest.TestCase)):
    """Base class for all our other tests.

    We don't really need this, but it demonstrates that the
    metaclass gets applied to all subclasses too.
    """


class MyTest(BaseTestCase):

    def tearDown(self):
        super(MyTest, self).tearDown()
        print(self.test_failed)

    def test_something(self):
        self.fail('Bummer')

Там вероятны много случаев, которые это не обрабатывает правильно. Например, это правильно не обнаруживает отказавший подтесты или ожидаемые отказы. Я интересовался бы другими видами отказа этого, поэтому если Вы находите случай, который я не обрабатываю правильно, сообщаю мне в комментариях, и я изучу его.

<час>

<глоток> * , Если бы не было более легкого пути, я не сделал бы _tag_error закрытая функция ;-)

3
ответ дан 31 October 2019 в 13:56

Протестированный на python 3.7 - пример кода для получения информации провального утверждения, но может дать идею, как иметь дело с ошибками:

def tearDown(self):
    if self._outcome.errors[1][1] and hasattr(self._outcome.errors[1][1][1], 'actual'):
        print(self._testMethodName)
        print(self._outcome.errors[1][1][1].actual)
        print(self._outcome.errors[1][1][1].expected)
0
ответ дан 31 October 2019 в 13:56

Это решение для [1 145] версии Python 2.7 к 3,7 (самая высокая текущая версия) без любых декораторов или другой модификации в любом коде прежде tearDown. Все работает согласно встроенной классификации результатов. Также пропущенные тесты или expectedFailure распознаны правильно. Это оценивает результат текущего теста, не, сводка всех тестов передала до сих пор. Совместимый также с [1 116] pytest.

import unittest

class MyTest(unittest.TestCase):
    def tearDown(self):
        if hasattr(self, '_outcome'):  # Python 3.4+
            result = self.defaultTestResult()  # these 2 methods have no side effects
            self._feedErrorsToResult(result, self._outcome.errors)
        else:  # Python 3.2 - 3.3 or 3.0 - 3.1 and 2.7
            result = getattr(self, '_outcomeForDoCleanups', self._resultForDoCleanups)
        error = self.list2reason(result.errors)
        failure = self.list2reason(result.failures)
        ok = not error and not failure

        # demo:   report short info immediately (not important)
        if not ok:
            typ, text = ('ERROR', error) if error else ('FAIL', failure)
            msg = [x for x in text.split('\n')[1:] if not x.startswith(' ')][0]
            print("\n%s: %s\n     %s" % (typ, self.id(), msg))

    def list2reason(self, exc_list):
        if exc_list and exc_list[-1][0] is self:
            return exc_list[-1][1]

    # DEMO tests
    def test_success(self):
        self.assertEqual(1, 1)

    def test_fail(self):
        self.assertEqual(2, 1)

    def test_error(self):
        self.assertEqual(1 / 0, 1)

Комментарии: Только об исключениях единицы или нули (ошибка или отказ) нужно сообщить, потому что не больше может ожидаться прежде tearDown. Пакет unittest ожидает, что второе исключение может быть повышено разрушением. Поэтому списки errors и failures могут содержать только элементы единицы или нули вместе перед разрушением. Строки после "демонстрационного" комментария сообщают о коротком результате.

Демонстрационный вывод: (не важный)

$ python3.5 -m unittest test

EF.
ERROR: test.MyTest.test_error
     ZeroDivisionError: division by zero
FAIL: test.MyTest.test_fail
     AssertionError: 2 != 1

==========================================================
... skipped usual output from unittest with tracebacks ...
...
Ran 3 tests in 0.002s

FAILED (failures=1, errors=1)
<час>

Comparision к другим решениям - (относительно истории фиксации исходного репозитория Python):

<час>

Объясненный исходным репозиторием Python
= Lib/unittest/case.py =
Python v 2.7 - 3.3

class TestCase(object):
    ...
    def run(self, result=None):
        ...
        self._outcomeForDoCleanups = result   # Python 3.2, 3.3
        # self._resultForDoCleanups = result  # Python 2.7
        #                                     # Python 2.6 - no result saved
        ...
        try:
            testMethod()
        except...   # many times for different exception classes
            result.add...(self, sys.exc_info())  # _addSkip, addError, addFailure
        ...
        try:
            self.tearDown()
        ...

Примечание к Python v 3.4 - 3.6

    def run(self, result=None):
        ...
        # outocome is a context manager to catch and collect different exceptions
        self._outcome = outcome  
        ...
        with outcome...(self):
            testMethod()
        ...
        with outcome...(self): 
            self.tearDown() 
        ... 
        self._feedErrorsToResult(result, outcome.errors)

(путем чтения сообщений о фиксации Python): причина , почему результаты испытаний так отделяются от тестов, утечки памяти предотвращение. Каждая информация об исключении может получить доступ к кадрам неудавшегося состояния процесса включая все локальные переменные. Если кадр присвоен локальной переменной в блоке кода, который мог бы также перестать работать, то перекрестный перезабор памяти мог быть легко создан. Это не ужасно благодаря сборщику "мусора", но свободная память может, стал фрагментированным более быстро, чем если бы память будет освобождена правильно. Это - причина, почему информация об исключении и traceback преобразовываются очень скоро в строки и почему временные объекты как [1 114] инкапсулируются и не установлены ни на Один в finally блок, чтобы к утечкам памяти предотвращены.

34
ответ дан 31 October 2019 в 13:56

Python 2.7.

можно также получить результат после unittest.main ():

t = unittest.main(exit=False)
print t.result

или комплект использования:

suite.addTests(tests)
result = unittest.result.TestResult()
suite.run(result)
print result
1
ответ дан 31 October 2019 в 13:56

Название текущего теста может быть получено с unittest. TestCase.id () метод. Таким образом в разрушении можно проверить self.id ().

Пример показывает как:

  • находят, имеет ли текущий тест ошибку или отказ в списке ошибок или отказов
  • тестовый идентификатор печати с ПЕРЕДАЧЕЙ или СБОЕМ или ИСКЛЮЧЕНИЕМ

, Протестированный пример здесь работает с хорошим примером @scoffey.

def tearDown(self):
    result = "PASS"
    #### find and show result for current test
    # I did not find any nicer/neater way of comparing self.id() with test id stored in errors or failures lists :-7
    id = str(self.id()).split('.')[-1]
    # id() e.g. tup[0]:<__main__.MyTest testMethod=test_onePlusNoneIsNone>
    #           str(tup[0]):"test_onePlusOneEqualsThree (__main__.MyTest)"
    #           str(self.id()) = __main__.MyTest.test_onePlusNoneIsNone
    for tup in self.currentResult.failures:
        if str(tup[0]).startswith(id):
            print ' test %s failure:%s' % (self.id(), tup[1])
            ## DO TEST FAIL ACTION HERE
            result = "FAIL"
    for tup in self.currentResult.errors:
        if str(tup[0]).startswith(id):
            print ' test %s error:%s' % (self.id(), tup[1])
            ## DO TEST EXCEPTION ACTION HERE
            result = "EXCEPTION"

    print "Test:%s Result:%s" % (self.id(), result)

пример результата:

python run_scripts/tut2.py 2>&1 
E test __main__.MyTest.test_onePlusNoneIsNone error:Traceback (most recent call last):
  File "run_scripts/tut2.py", line 80, in test_onePlusNoneIsNone
    self.assertTrue(1 + None is None) # raises TypeError
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'

Test:__main__.MyTest.test_onePlusNoneIsNone Result:EXCEPTION
F test __main__.MyTest.test_onePlusOneEqualsThree failure:Traceback (most recent call last):
  File "run_scripts/tut2.py", line 77, in test_onePlusOneEqualsThree
    self.assertTrue(1 + 1 == 3) # fails
AssertionError: False is not true

Test:__main__.MyTest.test_onePlusOneEqualsThree Result:FAIL
Test:__main__.MyTest.test_onePlusOneEqualsTwo Result:PASS
.
======================================================================
ERROR: test_onePlusNoneIsNone (__main__.MyTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "run_scripts/tut2.py", line 80, in test_onePlusNoneIsNone
    self.assertTrue(1 + None is None) # raises TypeError
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'

======================================================================
FAIL: test_onePlusOneEqualsThree (__main__.MyTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "run_scripts/tut2.py", line 77, in test_onePlusOneEqualsThree
     self.assertTrue(1 + 1 == 3) # fails
AssertionError: False is not true

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=1, errors=1)
1
ответ дан 31 October 2019 в 13:56

Вдохновленный [1 123] ответ scoffey’s , я решил взять беспощадность к следующему уровню и придумал следующее.

Это работает и в ванили unittest, и в также, когда выполнено через nosetests, и также работает в версиях 2.7, 3.2, 3.3 Python, и 3.4 (я конкретно не протестировал 3.0, 3.1, или 3.5, как я, don’t установили их в данный момент, но если бы я читал исходный код правильно, то это должно работать в 3,5 также):

#! /usr/bin/env python

from __future__ import unicode_literals
import logging
import os
import sys
import unittest


# Log file to see squawks during testing
formatter = logging.Formatter(fmt='%(levelname)-8s %(name)s: %(message)s')
log_file = os.path.splitext(os.path.abspath(__file__))[0] + '.log'
handler = logging.FileHandler(log_file)
handler.setFormatter(formatter)
logging.root.addHandler(handler)
logging.root.setLevel(logging.DEBUG)
log = logging.getLogger(__name__)


PY = tuple(sys.version_info)[:3]


class SmartTestCase(unittest.TestCase):

    """Knows its state (pass/fail/error) by the time its tearDown is called."""

    def run(self, result):
        # Store the result on the class so tearDown can behave appropriately
        self.result = result.result if hasattr(result, 'result') else result
        if PY >= (3, 4, 0):
            self._feedErrorsToResultEarly = self._feedErrorsToResult
            self._feedErrorsToResult = lambda *args, **kwargs: None  # no-op
        super(SmartTestCase, self).run(result)

    @property
    def errored(self):
        if (3, 0, 0) <= PY < (3, 4, 0):
            return bool(self._outcomeForDoCleanups.errors)
        return self.id() in [case.id() for case, _ in self.result.errors]

    @property
    def failed(self):
        if (3, 0, 0) <= PY < (3, 4, 0):
            return bool(self._outcomeForDoCleanups.failures)
        return self.id() in [case.id() for case, _ in self.result.failures]

    @property
    def passed(self):
        return not (self.errored or self.failed)

    def tearDown(self):
        if PY >= (3, 4, 0):
            self._feedErrorsToResultEarly(self.result, self._outcome.errors)


class TestClass(SmartTestCase):

    def test_1(self):
        self.assertTrue(True)

    def test_2(self):
        self.assertFalse(True)

    def test_3(self):
        self.assertFalse(False)

    def test_4(self):
        self.assertTrue(False)

    def test_5(self):
        self.assertHerp('Derp')

    def tearDown(self):
        super(TestClass, self).tearDown()
        log.critical('---- RUNNING {} ... -----'.format(self.id()))
        if self.errored:
            log.critical('----- ERRORED -----')
        elif self.failed:
            log.critical('----- FAILED -----')
        else:
            log.critical('----- PASSED -----')


if __name__ == '__main__':
    unittest.main()

, Когда выполнено с [1 110]:

$ ./test.py -v
test_1 (__main__.TestClass) ... ok
test_2 (__main__.TestClass) ... FAIL
test_3 (__main__.TestClass) ... ok
test_4 (__main__.TestClass) ... FAIL
test_5 (__main__.TestClass) ... ERROR
[…]

$ cat ./test.log
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_1 ... -----
CRITICAL __main__: ----- PASSED -----
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_2 ... -----
CRITICAL __main__: ----- FAILED -----
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_3 ... -----
CRITICAL __main__: ----- PASSED -----
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_4 ... -----
CRITICAL __main__: ----- FAILED -----
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_5 ... -----
CRITICAL __main__: ----- ERRORED -----

, Когда выполнено с [1 111]:

$ nosetests ./test.py -v
test_1 (test.TestClass) ... ok
test_2 (test.TestClass) ... FAIL
test_3 (test.TestClass) ... ok
test_4 (test.TestClass) ... FAIL
test_5 (test.TestClass) ... ERROR

$ cat ./test.log
CRITICAL test: ---- RUNNING test.TestClass.test_1 ... -----
CRITICAL test: ----- PASSED -----
CRITICAL test: ---- RUNNING test.TestClass.test_2 ... -----
CRITICAL test: ----- FAILED -----
CRITICAL test: ---- RUNNING test.TestClass.test_3 ... -----
CRITICAL test: ----- PASSED -----
CRITICAL test: ---- RUNNING test.TestClass.test_4 ... -----
CRITICAL test: ----- FAILED -----
CRITICAL test: ---- RUNNING test.TestClass.test_5 ... -----
CRITICAL test: ----- ERRORED -----

Фон

я запустил с этого:

class SmartTestCase(unittest.TestCase):

    """Knows its state (pass/fail/error) by the time its tearDown is called."""

    def run(self, result):
        # Store the result on the class so tearDown can behave appropriately
        self.result = result.result if hasattr(result, 'result') else result
        super(SmartTestCase, self).run(result)

    @property
    def errored(self):
        return self.id() in [case.id() for case, _ in self.result.errors]

    @property
    def failed(self):
        return self.id() in [case.id() for case, _ in self.result.failures]

    @property
    def passed(self):
        return not (self.errored or self.failed)

Однако это только работает в Python 2. В Python 3, до и включая 3,3, поток управления, кажется, изменился немного: Python 3’s unittest пакет результаты процессов после вызов каждого test’s tearDown() method†¦, это поведение может быть подтверждено, если мы просто добавляем дополнительную строку (или шесть) к нашему тестовому классу:

@@ -63,6 +63,12 @@
             log.critical('----- FAILED -----')
         else:
             log.critical('----- PASSED -----')
+        log.warning(
+            'ERRORS THUS FAR:\n'
+            + '\n'.join(tc.id() for tc, _ in self.result.errors))
+        log.warning(
+            'FAILURES THUS FAR:\n'
+            + '\n'.join(tc.id() for tc, _ in self.result.failures))


 if __name__ == '__main__':

Затем просто повторно выполняет тесты:

$ python3.3 ./test.py -v
test_1 (__main__.TestClass) ... ok
test_2 (__main__.TestClass) ... FAIL
test_3 (__main__.TestClass) ... ok
test_4 (__main__.TestClass) ... FAIL
test_5 (__main__.TestClass) ... ERROR
[…]

†¦ и Вы будете видеть, что получаете это в результате:

CRITICAL __main__: ---- RUNNING __main__.TestClass.test_1 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:

CRITICAL __main__: ---- RUNNING __main__.TestClass.test_2 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:

CRITICAL __main__: ---- RUNNING __main__.TestClass.test_3 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_4 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_5 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
__main__.TestClass.test_4

Теперь, сравните вышеупомянутое с выводом Python 2’s:

CRITICAL __main__: ---- RUNNING __main__.TestClass.test_1 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:

CRITICAL __main__: ---- RUNNING __main__.TestClass.test_2 ... -----
CRITICAL __main__: ----- FAILED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_3 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_4 ... -----
CRITICAL __main__: ----- FAILED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
__main__.TestClass.test_4
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_5 ... -----
CRITICAL __main__: ----- ERRORED -----
WARNING  __main__: ERRORS THUS FAR:
__main__.TestClass.test_5
WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
__main__.TestClass.test_4

, Так как Python 3 обрабатывает ошибки/отказы после , тест разъединяется, мы, can’t с готовностью выводят результат теста с помощью [1 113] или result.failures в каждом случае. (Я думаю, что, вероятно, имеет больше смысла архитектурно обрабатывать результаты test’s после разъединение его, однако, это делает , делают совершенно допустимый пример использования следующих другим концом процедуры тестирования в зависимости от состояния передачи/сбоя test’s немного тяжелее к meet†¦)

Поэтому вместо того, чтобы полагаться на полное result объект, вместо этого мы можем сослаться _outcomeForDoCleanups как [1 126], другие уже имеют упомянутый, который содержит объект результата для в настоящее время запускающего теста и имеет необходимое errors и failrues атрибуты, которые мы можем использовать для выведения test’s состояния к тому времени, когда tearDown() был назван:

@@ -3,6 +3,7 @@
 from __future__ import unicode_literals
 import logging
 import os
+import sys
 import unittest


@@ -16,6 +17,9 @@
 log = logging.getLogger(__name__)


+PY = tuple(sys.version_info)[:3]
+
+
 class SmartTestCase(unittest.TestCase):

     """Knows its state (pass/fail/error) by the time its tearDown is called."""
@@ -27,10 +31,14 @@

     @property
     def errored(self):
+        if PY >= (3, 0, 0):
+            return bool(self._outcomeForDoCleanups.errors)
         return self.id() in [case.id() for case, _ in self.result.errors]

     @property
     def failed(self):
+        if PY >= (3, 0, 0):
+            return bool(self._outcomeForDoCleanups.failures)
         return self.id() in [case.id() for case, _ in self.result.failures]

     @property

Это добавляет поддержку ранних версий Python 3.

С Python 3.4, однако, эта переменная члена парламента, не занимающего официального поста больше не существует , и вместо этого, новое (хотя также частный), метод был добавлен: _feedErrorsToResult .

Это означает, что для версий 3.4 ( и позже ), если потребность является достаточно большой, каждый может — очень hackishly — сила one’s путь в сделать, все это работать снова как он сделало в версии 2†¦

@@ -27,17 +27,20 @@
     def run(self, result):
         # Store the result on the class so tearDown can behave appropriately
         self.result = result.result if hasattr(result, 'result') else result
+        if PY >= (3, 4, 0):
+            self._feedErrorsToResultEarly = self._feedErrorsToResult
+            self._feedErrorsToResult = lambda *args, **kwargs: None  # no-op
         super(SmartTestCase, self).run(result)

     @property
     def errored(self):
-        if PY >= (3, 0, 0):
+        if (3, 0, 0) <= PY < (3, 4, 0):
             return bool(self._outcomeForDoCleanups.errors)
         return self.id() in [case.id() for case, _ in self.result.errors]

     @property
     def failed(self):
-        if PY >= (3, 0, 0):
+        if (3, 0, 0) <= PY < (3, 4, 0):
             return bool(self._outcomeForDoCleanups.failures)
         return self.id() in [case.id() for case, _ in self.result.failures]

@@ -45,6 +48,10 @@
     def passed(self):
         return not (self.errored or self.failed)

+    def tearDown(self):
+        if PY >= (3, 4, 0):
+            self._feedErrorsToResultEarly(self.result, self._outcome.errors)
+

 class TestClass(SmartTestCase):

@@ -64,6 +71,7 @@
         self.assertHerp('Derp')

     def tearDown(self):
+        super(TestClass, self).tearDown()
         log.critical('---- RUNNING {} ... -----'.format(self.id()))
         if self.errored:
             log.critical('----- ERRORED -----')

†¦, если, , конечно, все потребители этого класса помнят к [1 121] в их соответствующем tearDown methods†¦

Правовая оговорка: Чисто образовательный, don’t пробуют это дома, и т.д. и т.д. и т.д. I’m, не особенно гордящийся этим решением, но это, кажется, работает достаточно хорошо в настоящее время и является лучшим, чтобы я мог изрубить после игры в течение часа или два в субботу afternoon†¦

1
ответ дан 31 October 2019 в 13:56

Это зависит, какое создание отчетов требуется произвести.

В случае, если требуется сделать некоторые действия с отказом (такой как генерация снимки экрана ), вместо того, чтобы использовать tearDown(), можно достигнуть этого путем переопределения failureException.

, Например:

@property
def failureException(self):
    class MyFailureException(AssertionError):
        def __init__(self_, *args, **kwargs):
            screenshot_dir = 'reports/screenshots'
            if not os.path.exists(screenshot_dir):
                os.makedirs(screenshot_dir)
            self.driver.save_screenshot('{0}/{1}.png'.format(screenshot_dir, self.id()))
            return super(MyFailureException, self_).__init__(*args, **kwargs)
    MyFailureException.__name__ = AssertionError.__name__
    return MyFailureException
8
ответ дан 31 October 2019 в 13:56

Другие вопросы по тегам:

Похожие вопросы: