Best Python code snippet using pytest
test_junitxml.py
Source:test_junitxml.py
...22 fn = Path(__file__).parent / "example_scripts/junit-10.xsd"23 with fn.open() as f:24 return xmlschema.XMLSchema(f)25@pytest.fixture26def run_and_parse(testdir, schema):27 """Fixture that returns a function that can be used to execute pytest and28 return the parsed ``DomNode`` of the root xml node.29 The ``family`` parameter is used to configure the ``junit_family`` of the written report.30 "xunit2" is also automatically validated against the schema.31 """32 def run(*args, family="xunit1"):33 if family:34 args = ("-o", "junit_family=" + family) + args35 xml_path = testdir.tmpdir.join("junit.xml")36 result = testdir.runpytest("--junitxml=%s" % xml_path, *args)37 if family == "xunit2":38 with xml_path.open() as f:39 schema.validate(f)40 xmldoc = minidom.parse(str(xml_path))41 return result, DomNode(xmldoc)42 return run43def assert_attr(node, **kwargs):44 __tracebackhide__ = True45 def nodeval(node, name):46 anode = node.getAttributeNode(name)47 if anode is not None:48 return anode.value49 expected = {name: str(value) for name, value in kwargs.items()}50 on_node = {name: nodeval(node, name) for name in expected}51 assert on_node == expected52class DomNode:53 def __init__(self, dom):54 self.__node = dom55 def __repr__(self):56 return self.__node.toxml()57 def find_first_by_tag(self, tag):58 return self.find_nth_by_tag(tag, 0)59 def _by_tag(self, tag):60 return self.__node.getElementsByTagName(tag)61 @property62 def children(self):63 return [type(self)(x) for x in self.__node.childNodes]64 @property65 def get_unique_child(self):66 children = self.children67 assert len(children) == 168 return children[0]69 def find_nth_by_tag(self, tag, n):70 items = self._by_tag(tag)71 try:72 nth = items[n]73 except IndexError:74 pass75 else:76 return type(self)(nth)77 def find_by_tag(self, tag):78 t = type(self)79 return [t(x) for x in self.__node.getElementsByTagName(tag)]80 def __getitem__(self, key):81 node = self.__node.getAttributeNode(key)82 if node is not None:83 return node.value84 def assert_attr(self, **kwargs):85 __tracebackhide__ = True86 return assert_attr(self.__node, **kwargs)87 def toxml(self):88 return self.__node.toxml()89 @property90 def text(self):91 return self.__node.childNodes[0].wholeText92 @property93 def tag(self):94 return self.__node.tagName95 @property96 def next_sibling(self):97 return type(self)(self.__node.nextSibling)98parametrize_families = pytest.mark.parametrize("xunit_family", ["xunit1", "xunit2"])99class TestPython:100 @parametrize_families101 def test_summing_simple(self, testdir, run_and_parse, xunit_family):102 testdir.makepyfile(103 """104 import pytest105 def test_pass():106 pass107 def test_fail():108 assert 0109 def test_skip():110 pytest.skip("")111 @pytest.mark.xfail112 def test_xfail():113 assert 0114 @pytest.mark.xfail115 def test_xpass():116 assert 1117 """118 )119 result, dom = run_and_parse(family=xunit_family)120 assert result.ret121 node = dom.find_first_by_tag("testsuite")122 node.assert_attr(name="pytest", errors=0, failures=1, skipped=2, tests=5)123 @parametrize_families124 def test_summing_simple_with_errors(self, testdir, run_and_parse, xunit_family):125 testdir.makepyfile(126 """127 import pytest128 @pytest.fixture129 def fixture():130 raise Exception()131 def test_pass():132 pass133 def test_fail():134 assert 0135 def test_error(fixture):136 pass137 @pytest.mark.xfail138 def test_xfail():139 assert False140 @pytest.mark.xfail(strict=True)141 def test_xpass():142 assert True143 """144 )145 result, dom = run_and_parse(family=xunit_family)146 assert result.ret147 node = dom.find_first_by_tag("testsuite")148 node.assert_attr(name="pytest", errors=1, failures=2, skipped=1, tests=5)149 @parametrize_families150 def test_hostname_in_xml(self, testdir, run_and_parse, xunit_family):151 testdir.makepyfile(152 """153 def test_pass():154 pass155 """156 )157 result, dom = run_and_parse(family=xunit_family)158 node = dom.find_first_by_tag("testsuite")159 node.assert_attr(hostname=platform.node())160 @parametrize_families161 def test_timestamp_in_xml(self, testdir, run_and_parse, xunit_family):162 testdir.makepyfile(163 """164 def test_pass():165 pass166 """167 )168 start_time = datetime.now()169 result, dom = run_and_parse(family=xunit_family)170 node = dom.find_first_by_tag("testsuite")171 timestamp = datetime.strptime(node["timestamp"], "%Y-%m-%dT%H:%M:%S.%f")172 assert start_time <= timestamp < datetime.now()173 def test_timing_function(self, testdir, run_and_parse, mock_timing):174 testdir.makepyfile(175 """176 from _pytest import timing177 def setup_module():178 timing.sleep(1)179 def teardown_module():180 timing.sleep(2)181 def test_sleep():182 timing.sleep(4)183 """184 )185 result, dom = run_and_parse()186 node = dom.find_first_by_tag("testsuite")187 tnode = node.find_first_by_tag("testcase")188 val = tnode["time"]189 assert float(val) == 7.0190 @pytest.mark.parametrize("duration_report", ["call", "total"])191 def test_junit_duration_report(192 self, testdir, monkeypatch, duration_report, run_and_parse193 ):194 # mock LogXML.node_reporter so it always sets a known duration to each test report object195 original_node_reporter = LogXML.node_reporter196 def node_reporter_wrapper(s, report):197 report.duration = 1.0198 reporter = original_node_reporter(s, report)199 return reporter200 monkeypatch.setattr(LogXML, "node_reporter", node_reporter_wrapper)201 testdir.makepyfile(202 """203 def test_foo():204 pass205 """206 )207 result, dom = run_and_parse(208 "-o", "junit_duration_report={}".format(duration_report)209 )210 node = dom.find_first_by_tag("testsuite")211 tnode = node.find_first_by_tag("testcase")212 val = float(tnode["time"])213 if duration_report == "total":214 assert val == 3.0215 else:216 assert duration_report == "call"217 assert val == 1.0218 @parametrize_families219 def test_setup_error(self, testdir, run_and_parse, xunit_family):220 testdir.makepyfile(221 """222 import pytest223 @pytest.fixture224 def arg(request):225 raise ValueError("Error reason")226 def test_function(arg):227 pass228 """229 )230 result, dom = run_and_parse(family=xunit_family)231 assert result.ret232 node = dom.find_first_by_tag("testsuite")233 node.assert_attr(errors=1, tests=1)234 tnode = node.find_first_by_tag("testcase")235 tnode.assert_attr(classname="test_setup_error", name="test_function")236 fnode = tnode.find_first_by_tag("error")237 fnode.assert_attr(message='failed on setup with "ValueError: Error reason"')238 assert "ValueError" in fnode.toxml()239 @parametrize_families240 def test_teardown_error(self, testdir, run_and_parse, xunit_family):241 testdir.makepyfile(242 """243 import pytest244 @pytest.fixture245 def arg():246 yield247 raise ValueError('Error reason')248 def test_function(arg):249 pass250 """251 )252 result, dom = run_and_parse(family=xunit_family)253 assert result.ret254 node = dom.find_first_by_tag("testsuite")255 tnode = node.find_first_by_tag("testcase")256 tnode.assert_attr(classname="test_teardown_error", name="test_function")257 fnode = tnode.find_first_by_tag("error")258 fnode.assert_attr(message='failed on teardown with "ValueError: Error reason"')259 assert "ValueError" in fnode.toxml()260 @parametrize_families261 def test_call_failure_teardown_error(self, testdir, run_and_parse, xunit_family):262 testdir.makepyfile(263 """264 import pytest265 @pytest.fixture266 def arg():267 yield268 raise Exception("Teardown Exception")269 def test_function(arg):270 raise Exception("Call Exception")271 """272 )273 result, dom = run_and_parse(family=xunit_family)274 assert result.ret275 node = dom.find_first_by_tag("testsuite")276 node.assert_attr(errors=1, failures=1, tests=1)277 first, second = dom.find_by_tag("testcase")278 assert first279 assert second280 assert first != second281 fnode = first.find_first_by_tag("failure")282 fnode.assert_attr(message="Exception: Call Exception")283 snode = second.find_first_by_tag("error")284 snode.assert_attr(285 message='failed on teardown with "Exception: Teardown Exception"'286 )287 @parametrize_families288 def test_skip_contains_name_reason(self, testdir, run_and_parse, xunit_family):289 testdir.makepyfile(290 """291 import pytest292 def test_skip():293 pytest.skip("hello23")294 """295 )296 result, dom = run_and_parse(family=xunit_family)297 assert result.ret == 0298 node = dom.find_first_by_tag("testsuite")299 node.assert_attr(skipped=1)300 tnode = node.find_first_by_tag("testcase")301 tnode.assert_attr(classname="test_skip_contains_name_reason", name="test_skip")302 snode = tnode.find_first_by_tag("skipped")303 snode.assert_attr(type="pytest.skip", message="hello23")304 @parametrize_families305 def test_mark_skip_contains_name_reason(self, testdir, run_and_parse, xunit_family):306 testdir.makepyfile(307 """308 import pytest309 @pytest.mark.skip(reason="hello24")310 def test_skip():311 assert True312 """313 )314 result, dom = run_and_parse(family=xunit_family)315 assert result.ret == 0316 node = dom.find_first_by_tag("testsuite")317 node.assert_attr(skipped=1)318 tnode = node.find_first_by_tag("testcase")319 tnode.assert_attr(320 classname="test_mark_skip_contains_name_reason", name="test_skip"321 )322 snode = tnode.find_first_by_tag("skipped")323 snode.assert_attr(type="pytest.skip", message="hello24")324 @parametrize_families325 def test_mark_skipif_contains_name_reason(326 self, testdir, run_and_parse, xunit_family327 ):328 testdir.makepyfile(329 """330 import pytest331 GLOBAL_CONDITION = True332 @pytest.mark.skipif(GLOBAL_CONDITION, reason="hello25")333 def test_skip():334 assert True335 """336 )337 result, dom = run_and_parse(family=xunit_family)338 assert result.ret == 0339 node = dom.find_first_by_tag("testsuite")340 node.assert_attr(skipped=1)341 tnode = node.find_first_by_tag("testcase")342 tnode.assert_attr(343 classname="test_mark_skipif_contains_name_reason", name="test_skip"344 )345 snode = tnode.find_first_by_tag("skipped")346 snode.assert_attr(type="pytest.skip", message="hello25")347 @parametrize_families348 def test_mark_skip_doesnt_capture_output(349 self, testdir, run_and_parse, xunit_family350 ):351 testdir.makepyfile(352 """353 import pytest354 @pytest.mark.skip(reason="foo")355 def test_skip():356 print("bar!")357 """358 )359 result, dom = run_and_parse(family=xunit_family)360 assert result.ret == 0361 node_xml = dom.find_first_by_tag("testsuite").toxml()362 assert "bar!" not in node_xml363 @parametrize_families364 def test_classname_instance(self, testdir, run_and_parse, xunit_family):365 testdir.makepyfile(366 """367 class TestClass(object):368 def test_method(self):369 assert 0370 """371 )372 result, dom = run_and_parse(family=xunit_family)373 assert result.ret374 node = dom.find_first_by_tag("testsuite")375 node.assert_attr(failures=1)376 tnode = node.find_first_by_tag("testcase")377 tnode.assert_attr(378 classname="test_classname_instance.TestClass", name="test_method"379 )380 @parametrize_families381 def test_classname_nested_dir(self, testdir, run_and_parse, xunit_family):382 p = testdir.tmpdir.ensure("sub", "test_hello.py")383 p.write("def test_func(): 0/0")384 result, dom = run_and_parse(family=xunit_family)385 assert result.ret386 node = dom.find_first_by_tag("testsuite")387 node.assert_attr(failures=1)388 tnode = node.find_first_by_tag("testcase")389 tnode.assert_attr(classname="sub.test_hello", name="test_func")390 @parametrize_families391 def test_internal_error(self, testdir, run_and_parse, xunit_family):392 testdir.makeconftest("def pytest_runtest_protocol(): 0 / 0")393 testdir.makepyfile("def test_function(): pass")394 result, dom = run_and_parse(family=xunit_family)395 assert result.ret396 node = dom.find_first_by_tag("testsuite")397 node.assert_attr(errors=1, tests=1)398 tnode = node.find_first_by_tag("testcase")399 tnode.assert_attr(classname="pytest", name="internal")400 fnode = tnode.find_first_by_tag("error")401 fnode.assert_attr(message="internal error")402 assert "Division" in fnode.toxml()403 @pytest.mark.parametrize(404 "junit_logging", ["no", "log", "system-out", "system-err", "out-err", "all"]405 )406 @parametrize_families407 def test_failure_function(408 self, testdir, junit_logging, run_and_parse, xunit_family409 ):410 testdir.makepyfile(411 """412 import logging413 import sys414 def test_fail():415 print("hello-stdout")416 sys.stderr.write("hello-stderr\\n")417 logging.info('info msg')418 logging.warning('warning msg')419 raise ValueError(42)420 """421 )422 result, dom = run_and_parse(423 "-o", "junit_logging=%s" % junit_logging, family=xunit_family424 )425 assert result.ret, "Expected ret > 0"426 node = dom.find_first_by_tag("testsuite")427 node.assert_attr(failures=1, tests=1)428 tnode = node.find_first_by_tag("testcase")429 tnode.assert_attr(classname="test_failure_function", name="test_fail")430 fnode = tnode.find_first_by_tag("failure")431 fnode.assert_attr(message="ValueError: 42")432 assert "ValueError" in fnode.toxml(), "ValueError not included"433 if junit_logging in ["log", "all"]:434 logdata = tnode.find_first_by_tag("system-out")435 log_xml = logdata.toxml()436 assert logdata.tag == "system-out", "Expected tag: system-out"437 assert "info msg" not in log_xml, "Unexpected INFO message"438 assert "warning msg" in log_xml, "Missing WARN message"439 if junit_logging in ["system-out", "out-err", "all"]:440 systemout = tnode.find_first_by_tag("system-out")441 systemout_xml = systemout.toxml()442 assert systemout.tag == "system-out", "Expected tag: system-out"443 assert "info msg" not in systemout_xml, "INFO message found in system-out"444 assert (445 "hello-stdout" in systemout_xml446 ), "Missing 'hello-stdout' in system-out"447 if junit_logging in ["system-err", "out-err", "all"]:448 systemerr = tnode.find_first_by_tag("system-err")449 systemerr_xml = systemerr.toxml()450 assert systemerr.tag == "system-err", "Expected tag: system-err"451 assert "info msg" not in systemerr_xml, "INFO message found in system-err"452 assert (453 "hello-stderr" in systemerr_xml454 ), "Missing 'hello-stderr' in system-err"455 assert (456 "warning msg" not in systemerr_xml457 ), "WARN message found in system-err"458 if junit_logging == "no":459 assert not tnode.find_by_tag("log"), "Found unexpected content: log"460 assert not tnode.find_by_tag(461 "system-out"462 ), "Found unexpected content: system-out"463 assert not tnode.find_by_tag(464 "system-err"465 ), "Found unexpected content: system-err"466 @parametrize_families467 def test_failure_verbose_message(self, testdir, run_and_parse, xunit_family):468 testdir.makepyfile(469 """470 import sys471 def test_fail():472 assert 0, "An error"473 """474 )475 result, dom = run_and_parse(family=xunit_family)476 node = dom.find_first_by_tag("testsuite")477 tnode = node.find_first_by_tag("testcase")478 fnode = tnode.find_first_by_tag("failure")479 fnode.assert_attr(message="AssertionError: An error\nassert 0")480 @parametrize_families481 def test_failure_escape(self, testdir, run_and_parse, xunit_family):482 testdir.makepyfile(483 """484 import pytest485 @pytest.mark.parametrize('arg1', "<&'", ids="<&'")486 def test_func(arg1):487 print(arg1)488 assert 0489 """490 )491 result, dom = run_and_parse(492 "-o", "junit_logging=system-out", family=xunit_family493 )494 assert result.ret495 node = dom.find_first_by_tag("testsuite")496 node.assert_attr(failures=3, tests=3)497 for index, char in enumerate("<&'"):498 tnode = node.find_nth_by_tag("testcase", index)499 tnode.assert_attr(500 classname="test_failure_escape", name="test_func[%s]" % char501 )502 sysout = tnode.find_first_by_tag("system-out")503 text = sysout.text504 assert "%s\n" % char in text505 @parametrize_families506 def test_junit_prefixing(self, testdir, run_and_parse, xunit_family):507 testdir.makepyfile(508 """509 def test_func():510 assert 0511 class TestHello(object):512 def test_hello(self):513 pass514 """515 )516 result, dom = run_and_parse("--junitprefix=xyz", family=xunit_family)517 assert result.ret518 node = dom.find_first_by_tag("testsuite")519 node.assert_attr(failures=1, tests=2)520 tnode = node.find_first_by_tag("testcase")521 tnode.assert_attr(classname="xyz.test_junit_prefixing", name="test_func")522 tnode = node.find_nth_by_tag("testcase", 1)523 tnode.assert_attr(524 classname="xyz.test_junit_prefixing.TestHello", name="test_hello"525 )526 @parametrize_families527 def test_xfailure_function(self, testdir, run_and_parse, xunit_family):528 testdir.makepyfile(529 """530 import pytest531 def test_xfail():532 pytest.xfail("42")533 """534 )535 result, dom = run_and_parse(family=xunit_family)536 assert not result.ret537 node = dom.find_first_by_tag("testsuite")538 node.assert_attr(skipped=1, tests=1)539 tnode = node.find_first_by_tag("testcase")540 tnode.assert_attr(classname="test_xfailure_function", name="test_xfail")541 fnode = tnode.find_first_by_tag("skipped")542 fnode.assert_attr(type="pytest.xfail", message="42")543 @parametrize_families544 def test_xfailure_marker(self, testdir, run_and_parse, xunit_family):545 testdir.makepyfile(546 """547 import pytest548 @pytest.mark.xfail(reason="42")549 def test_xfail():550 assert False551 """552 )553 result, dom = run_and_parse(family=xunit_family)554 assert not result.ret555 node = dom.find_first_by_tag("testsuite")556 node.assert_attr(skipped=1, tests=1)557 tnode = node.find_first_by_tag("testcase")558 tnode.assert_attr(classname="test_xfailure_marker", name="test_xfail")559 fnode = tnode.find_first_by_tag("skipped")560 fnode.assert_attr(type="pytest.xfail", message="42")561 @pytest.mark.parametrize(562 "junit_logging", ["no", "log", "system-out", "system-err", "out-err", "all"]563 )564 def test_xfail_captures_output_once(self, testdir, junit_logging, run_and_parse):565 testdir.makepyfile(566 """567 import sys568 import pytest569 @pytest.mark.xfail()570 def test_fail():571 sys.stdout.write('XFAIL This is stdout')572 sys.stderr.write('XFAIL This is stderr')573 assert 0574 """575 )576 result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging)577 node = dom.find_first_by_tag("testsuite")578 tnode = node.find_first_by_tag("testcase")579 if junit_logging in ["system-err", "out-err", "all"]:580 assert len(tnode.find_by_tag("system-err")) == 1581 else:582 assert len(tnode.find_by_tag("system-err")) == 0583 if junit_logging in ["log", "system-out", "out-err", "all"]:584 assert len(tnode.find_by_tag("system-out")) == 1585 else:586 assert len(tnode.find_by_tag("system-out")) == 0587 @parametrize_families588 def test_xfailure_xpass(self, testdir, run_and_parse, xunit_family):589 testdir.makepyfile(590 """591 import pytest592 @pytest.mark.xfail593 def test_xpass():594 pass595 """596 )597 result, dom = run_and_parse(family=xunit_family)598 # assert result.ret599 node = dom.find_first_by_tag("testsuite")600 node.assert_attr(skipped=0, tests=1)601 tnode = node.find_first_by_tag("testcase")602 tnode.assert_attr(classname="test_xfailure_xpass", name="test_xpass")603 @parametrize_families604 def test_xfailure_xpass_strict(self, testdir, run_and_parse, xunit_family):605 testdir.makepyfile(606 """607 import pytest608 @pytest.mark.xfail(strict=True, reason="This needs to fail!")609 def test_xpass():610 pass611 """612 )613 result, dom = run_and_parse(family=xunit_family)614 # assert result.ret615 node = dom.find_first_by_tag("testsuite")616 node.assert_attr(skipped=0, tests=1)617 tnode = node.find_first_by_tag("testcase")618 tnode.assert_attr(classname="test_xfailure_xpass_strict", name="test_xpass")619 fnode = tnode.find_first_by_tag("failure")620 fnode.assert_attr(message="[XPASS(strict)] This needs to fail!")621 @parametrize_families622 def test_collect_error(self, testdir, run_and_parse, xunit_family):623 testdir.makepyfile("syntax error")624 result, dom = run_and_parse(family=xunit_family)625 assert result.ret626 node = dom.find_first_by_tag("testsuite")627 node.assert_attr(errors=1, tests=1)628 tnode = node.find_first_by_tag("testcase")629 fnode = tnode.find_first_by_tag("error")630 fnode.assert_attr(message="collection failure")631 assert "SyntaxError" in fnode.toxml()632 def test_unicode(self, testdir, run_and_parse):633 value = "hx\xc4\x85\xc4\x87\n"634 testdir.makepyfile(635 """\636 # coding: latin1637 def test_hello():638 print(%r)639 assert 0640 """641 % value642 )643 result, dom = run_and_parse()644 assert result.ret == 1645 tnode = dom.find_first_by_tag("testcase")646 fnode = tnode.find_first_by_tag("failure")647 assert "hx" in fnode.toxml()648 def test_assertion_binchars(self, testdir, run_and_parse):649 """This test did fail when the escaping wasn't strict."""650 testdir.makepyfile(651 """652 M1 = '\x01\x02\x03\x04'653 M2 = '\x01\x02\x03\x05'654 def test_str_compare():655 assert M1 == M2656 """657 )658 result, dom = run_and_parse()659 print(dom.toxml())660 @pytest.mark.parametrize("junit_logging", ["no", "system-out"])661 def test_pass_captures_stdout(self, testdir, run_and_parse, junit_logging):662 testdir.makepyfile(663 """664 def test_pass():665 print('hello-stdout')666 """667 )668 result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging)669 node = dom.find_first_by_tag("testsuite")670 pnode = node.find_first_by_tag("testcase")671 if junit_logging == "no":672 assert not node.find_by_tag(673 "system-out"674 ), "system-out should not be generated"675 if junit_logging == "system-out":676 systemout = pnode.find_first_by_tag("system-out")677 assert (678 "hello-stdout" in systemout.toxml()679 ), "'hello-stdout' should be in system-out"680 @pytest.mark.parametrize("junit_logging", ["no", "system-err"])681 def test_pass_captures_stderr(self, testdir, run_and_parse, junit_logging):682 testdir.makepyfile(683 """684 import sys685 def test_pass():686 sys.stderr.write('hello-stderr')687 """688 )689 result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging)690 node = dom.find_first_by_tag("testsuite")691 pnode = node.find_first_by_tag("testcase")692 if junit_logging == "no":693 assert not node.find_by_tag(694 "system-err"695 ), "system-err should not be generated"696 if junit_logging == "system-err":697 systemerr = pnode.find_first_by_tag("system-err")698 assert (699 "hello-stderr" in systemerr.toxml()700 ), "'hello-stderr' should be in system-err"701 @pytest.mark.parametrize("junit_logging", ["no", "system-out"])702 def test_setup_error_captures_stdout(self, testdir, run_and_parse, junit_logging):703 testdir.makepyfile(704 """705 import pytest706 @pytest.fixture707 def arg(request):708 print('hello-stdout')709 raise ValueError()710 def test_function(arg):711 pass712 """713 )714 result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging)715 node = dom.find_first_by_tag("testsuite")716 pnode = node.find_first_by_tag("testcase")717 if junit_logging == "no":718 assert not node.find_by_tag(719 "system-out"720 ), "system-out should not be generated"721 if junit_logging == "system-out":722 systemout = pnode.find_first_by_tag("system-out")723 assert (724 "hello-stdout" in systemout.toxml()725 ), "'hello-stdout' should be in system-out"726 @pytest.mark.parametrize("junit_logging", ["no", "system-err"])727 def test_setup_error_captures_stderr(self, testdir, run_and_parse, junit_logging):728 testdir.makepyfile(729 """730 import sys731 import pytest732 @pytest.fixture733 def arg(request):734 sys.stderr.write('hello-stderr')735 raise ValueError()736 def test_function(arg):737 pass738 """739 )740 result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging)741 node = dom.find_first_by_tag("testsuite")742 pnode = node.find_first_by_tag("testcase")743 if junit_logging == "no":744 assert not node.find_by_tag(745 "system-err"746 ), "system-err should not be generated"747 if junit_logging == "system-err":748 systemerr = pnode.find_first_by_tag("system-err")749 assert (750 "hello-stderr" in systemerr.toxml()751 ), "'hello-stderr' should be in system-err"752 @pytest.mark.parametrize("junit_logging", ["no", "system-out"])753 def test_avoid_double_stdout(self, testdir, run_and_parse, junit_logging):754 testdir.makepyfile(755 """756 import sys757 import pytest758 @pytest.fixture759 def arg(request):760 yield761 sys.stdout.write('hello-stdout teardown')762 raise ValueError()763 def test_function(arg):764 sys.stdout.write('hello-stdout call')765 """766 )767 result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging)768 node = dom.find_first_by_tag("testsuite")769 pnode = node.find_first_by_tag("testcase")770 if junit_logging == "no":771 assert not node.find_by_tag(772 "system-out"773 ), "system-out should not be generated"774 if junit_logging == "system-out":775 systemout = pnode.find_first_by_tag("system-out")776 assert "hello-stdout call" in systemout.toxml()777 assert "hello-stdout teardown" in systemout.toxml()778def test_mangle_test_address():779 from _pytest.junitxml import mangle_test_address780 address = "::".join(["a/my.py.thing.py", "Class", "()", "method", "[a-1-::]"])781 newnames = mangle_test_address(address)782 assert newnames == ["a.my.py.thing", "Class", "method", "[a-1-::]"]783def test_dont_configure_on_workers(tmpdir) -> None:784 gotten = [] # type: List[object]785 class FakeConfig:786 if TYPE_CHECKING:787 workerinput = None788 def __init__(self):789 self.pluginmanager = self790 self.option = self791 self._store = Store()792 def getini(self, name):793 return "pytest"794 junitprefix = None795 # XXX: shouldn't need tmpdir ?796 xmlpath = str(tmpdir.join("junix.xml"))797 register = gotten.append798 fake_config = cast(Config, FakeConfig())799 from _pytest import junitxml800 junitxml.pytest_configure(fake_config)801 assert len(gotten) == 1802 FakeConfig.workerinput = None803 junitxml.pytest_configure(fake_config)804 assert len(gotten) == 1805class TestNonPython:806 @parametrize_families807 def test_summing_simple(self, testdir, run_and_parse, xunit_family):808 testdir.makeconftest(809 """810 import pytest811 def pytest_collect_file(path, parent):812 if path.ext == ".xyz":813 return MyItem.from_parent(name=path.basename, parent=parent)814 class MyItem(pytest.Item):815 def runtest(self):816 raise ValueError(42)817 def repr_failure(self, excinfo):818 return "custom item runtest failed"819 """820 )821 testdir.tmpdir.join("myfile.xyz").write("hello")822 result, dom = run_and_parse(family=xunit_family)823 assert result.ret824 node = dom.find_first_by_tag("testsuite")825 node.assert_attr(errors=0, failures=1, skipped=0, tests=1)826 tnode = node.find_first_by_tag("testcase")827 tnode.assert_attr(name="myfile.xyz")828 fnode = tnode.find_first_by_tag("failure")829 fnode.assert_attr(message="custom item runtest failed")830 assert "custom item runtest failed" in fnode.toxml()831@pytest.mark.parametrize("junit_logging", ["no", "system-out"])832def test_nullbyte(testdir, junit_logging):833 # A null byte can not occur in XML (see section 2.2 of the spec)834 testdir.makepyfile(835 """836 import sys837 def test_print_nullbyte():838 sys.stdout.write('Here the null -->' + chr(0) + '<--')839 sys.stdout.write('In repr form -->' + repr(chr(0)) + '<--')840 assert False841 """842 )843 xmlf = testdir.tmpdir.join("junit.xml")844 testdir.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging)845 text = xmlf.read()846 assert "\x00" not in text847 if junit_logging == "system-out":848 assert "#x00" in text849 if junit_logging == "no":850 assert "#x00" not in text851@pytest.mark.parametrize("junit_logging", ["no", "system-out"])852def test_nullbyte_replace(testdir, junit_logging):853 # Check if the null byte gets replaced854 testdir.makepyfile(855 """856 import sys857 def test_print_nullbyte():858 sys.stdout.write('Here the null -->' + chr(0) + '<--')859 sys.stdout.write('In repr form -->' + repr(chr(0)) + '<--')860 assert False861 """862 )863 xmlf = testdir.tmpdir.join("junit.xml")864 testdir.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging)865 text = xmlf.read()866 if junit_logging == "system-out":867 assert "#x0" in text868 if junit_logging == "no":869 assert "#x0" not in text870def test_invalid_xml_escape():871 # Test some more invalid xml chars, the full range should be872 # tested really but let's just test the edges of the ranges873 # instead.874 # XXX This only tests low unicode character points for now as875 # there are some issues with the testing infrastructure for876 # the higher ones.877 # XXX Testing 0xD (\r) is tricky as it overwrites the just written878 # line in the output, so we skip it too.879 invalid = (880 0x00,881 0x1,882 0xB,883 0xC,884 0xE,885 0x19,886 27, # issue #126887 0xD800,888 0xDFFF,889 0xFFFE,890 0x0FFFF,891 ) # , 0x110000)892 valid = (0x9, 0xA, 0x20)893 # 0xD, 0xD7FF, 0xE000, 0xFFFD, 0x10000, 0x10FFFF)894 for i in invalid:895 got = bin_xml_escape(chr(i))896 if i <= 0xFF:897 expected = "#x%02X" % i898 else:899 expected = "#x%04X" % i900 assert got == expected901 for i in valid:902 assert chr(i) == bin_xml_escape(chr(i))903def test_logxml_path_expansion(tmpdir, monkeypatch):904 home_tilde = py.path.local(os.path.expanduser("~")).join("test.xml")905 xml_tilde = LogXML("~%stest.xml" % tmpdir.sep, None)906 assert xml_tilde.logfile == home_tilde907 monkeypatch.setenv("HOME", str(tmpdir))908 home_var = os.path.normpath(os.path.expandvars("$HOME/test.xml"))909 xml_var = LogXML("$HOME%stest.xml" % tmpdir.sep, None)910 assert xml_var.logfile == home_var911def test_logxml_changingdir(testdir):912 testdir.makepyfile(913 """914 def test_func():915 import os916 os.chdir("a")917 """918 )919 testdir.tmpdir.mkdir("a")920 result = testdir.runpytest("--junitxml=a/x.xml")921 assert result.ret == 0922 assert testdir.tmpdir.join("a/x.xml").check()923def test_logxml_makedir(testdir):924 """--junitxml should automatically create directories for the xml file"""925 testdir.makepyfile(926 """927 def test_pass():928 pass929 """930 )931 result = testdir.runpytest("--junitxml=path/to/results.xml")932 assert result.ret == 0933 assert testdir.tmpdir.join("path/to/results.xml").check()934def test_logxml_check_isdir(testdir):935 """Give an error if --junit-xml is a directory (#2089)"""936 result = testdir.runpytest("--junit-xml=.")937 result.stderr.fnmatch_lines(["*--junitxml must be a filename*"])938def test_escaped_parametrized_names_xml(testdir, run_and_parse):939 testdir.makepyfile(940 """\941 import pytest942 @pytest.mark.parametrize('char', ["\\x00"])943 def test_func(char):944 assert char945 """946 )947 result, dom = run_and_parse()948 assert result.ret == 0949 node = dom.find_first_by_tag("testcase")950 node.assert_attr(name="test_func[\\x00]")951def test_double_colon_split_function_issue469(testdir, run_and_parse):952 testdir.makepyfile(953 """954 import pytest955 @pytest.mark.parametrize('param', ["double::colon"])956 def test_func(param):957 pass958 """959 )960 result, dom = run_and_parse()961 assert result.ret == 0962 node = dom.find_first_by_tag("testcase")963 node.assert_attr(classname="test_double_colon_split_function_issue469")964 node.assert_attr(name="test_func[double::colon]")965def test_double_colon_split_method_issue469(testdir, run_and_parse):966 testdir.makepyfile(967 """968 import pytest969 class TestClass(object):970 @pytest.mark.parametrize('param', ["double::colon"])971 def test_func(self, param):972 pass973 """974 )975 result, dom = run_and_parse()976 assert result.ret == 0977 node = dom.find_first_by_tag("testcase")978 node.assert_attr(classname="test_double_colon_split_method_issue469.TestClass")979 node.assert_attr(name="test_func[double::colon]")980def test_unicode_issue368(testdir) -> None:981 path = testdir.tmpdir.join("test.xml")982 log = LogXML(str(path), None)983 ustr = "ÐÐÐ!"984 class Report(BaseReport):985 longrepr = ustr986 sections = [] # type: List[Tuple[str, str]]987 nodeid = "something"988 location = "tests/filename.py", 42, "TestClass.method"989 test_report = cast(TestReport, Report())990 # hopefully this is not too brittle ...991 log.pytest_sessionstart()992 node_reporter = log._opentestcase(test_report)993 node_reporter.append_failure(test_report)994 node_reporter.append_collect_error(test_report)995 node_reporter.append_collect_skipped(test_report)996 node_reporter.append_error(test_report)997 test_report.longrepr = "filename", 1, ustr998 node_reporter.append_skipped(test_report)999 test_report.longrepr = "filename", 1, "Skipped: å¡å£å£"1000 node_reporter.append_skipped(test_report)1001 test_report.wasxfail = ustr # type: ignore[attr-defined]1002 node_reporter.append_skipped(test_report)1003 log.pytest_sessionfinish()1004def test_record_property(testdir, run_and_parse):1005 testdir.makepyfile(1006 """1007 import pytest1008 @pytest.fixture1009 def other(record_property):1010 record_property("bar", 1)1011 def test_record(record_property, other):1012 record_property("foo", "<1");1013 """1014 )1015 result, dom = run_and_parse()1016 node = dom.find_first_by_tag("testsuite")1017 tnode = node.find_first_by_tag("testcase")1018 psnode = tnode.find_first_by_tag("properties")1019 pnodes = psnode.find_by_tag("property")1020 pnodes[0].assert_attr(name="bar", value="1")1021 pnodes[1].assert_attr(name="foo", value="<1")1022 result.stdout.fnmatch_lines(["*= 1 passed in *"])1023def test_record_property_same_name(testdir, run_and_parse):1024 testdir.makepyfile(1025 """1026 def test_record_with_same_name(record_property):1027 record_property("foo", "bar")1028 record_property("foo", "baz")1029 """1030 )1031 result, dom = run_and_parse()1032 node = dom.find_first_by_tag("testsuite")1033 tnode = node.find_first_by_tag("testcase")1034 psnode = tnode.find_first_by_tag("properties")1035 pnodes = psnode.find_by_tag("property")1036 pnodes[0].assert_attr(name="foo", value="bar")1037 pnodes[1].assert_attr(name="foo", value="baz")1038@pytest.mark.parametrize("fixture_name", ["record_property", "record_xml_attribute"])1039def test_record_fixtures_without_junitxml(testdir, fixture_name):1040 testdir.makepyfile(1041 """1042 def test_record({fixture_name}):1043 {fixture_name}("foo", "bar")1044 """.format(1045 fixture_name=fixture_name1046 )1047 )1048 result = testdir.runpytest()1049 assert result.ret == 01050@pytest.mark.filterwarnings("default")1051def test_record_attribute(testdir, run_and_parse):1052 testdir.makeini(1053 """1054 [pytest]1055 junit_family = xunit11056 """1057 )1058 testdir.makepyfile(1059 """1060 import pytest1061 @pytest.fixture1062 def other(record_xml_attribute):1063 record_xml_attribute("bar", 1)1064 def test_record(record_xml_attribute, other):1065 record_xml_attribute("foo", "<1");1066 """1067 )1068 result, dom = run_and_parse()1069 node = dom.find_first_by_tag("testsuite")1070 tnode = node.find_first_by_tag("testcase")1071 tnode.assert_attr(bar="1")1072 tnode.assert_attr(foo="<1")1073 result.stdout.fnmatch_lines(1074 ["*test_record_attribute.py:6:*record_xml_attribute is an experimental feature"]1075 )1076@pytest.mark.filterwarnings("default")1077@pytest.mark.parametrize("fixture_name", ["record_xml_attribute", "record_property"])1078def test_record_fixtures_xunit2(testdir, fixture_name, run_and_parse):1079 """Ensure record_xml_attribute and record_property drop values when outside of legacy family."""1080 testdir.makeini(1081 """1082 [pytest]1083 junit_family = xunit21084 """1085 )1086 testdir.makepyfile(1087 """1088 import pytest1089 @pytest.fixture1090 def other({fixture_name}):1091 {fixture_name}("bar", 1)1092 def test_record({fixture_name}, other):1093 {fixture_name}("foo", "<1");1094 """.format(1095 fixture_name=fixture_name1096 )1097 )1098 result, dom = run_and_parse(family=None)1099 expected_lines = []1100 if fixture_name == "record_xml_attribute":1101 expected_lines.append(1102 "*test_record_fixtures_xunit2.py:6:*record_xml_attribute is an experimental feature"1103 )1104 expected_lines = [1105 "*test_record_fixtures_xunit2.py:6:*{fixture_name} is incompatible "1106 "with junit_family 'xunit2' (use 'legacy' or 'xunit1')".format(1107 fixture_name=fixture_name1108 )1109 ]1110 result.stdout.fnmatch_lines(expected_lines)1111def test_random_report_log_xdist(testdir, monkeypatch, run_and_parse):1112 """`xdist` calls pytest_runtest_logreport as they are executed by the workers,1113 with nodes from several nodes overlapping, so junitxml must cope with that1114 to produce correct reports (#1064)."""1115 pytest.importorskip("xdist")1116 monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)1117 testdir.makepyfile(1118 """1119 import pytest, time1120 @pytest.mark.parametrize('i', list(range(30)))1121 def test_x(i):1122 assert i != 221123 """1124 )1125 _, dom = run_and_parse("-n2")1126 suite_node = dom.find_first_by_tag("testsuite")1127 failed = []1128 for case_node in suite_node.find_by_tag("testcase"):1129 if case_node.find_first_by_tag("failure"):1130 failed.append(case_node["name"])1131 assert failed == ["test_x[22]"]1132@parametrize_families1133def test_root_testsuites_tag(testdir, run_and_parse, xunit_family):1134 testdir.makepyfile(1135 """1136 def test_x():1137 pass1138 """1139 )1140 _, dom = run_and_parse(family=xunit_family)1141 root = dom.get_unique_child1142 assert root.tag == "testsuites"1143 suite_node = root.get_unique_child1144 assert suite_node.tag == "testsuite"1145def test_runs_twice(testdir, run_and_parse):1146 f = testdir.makepyfile(1147 """1148 def test_pass():1149 pass1150 """1151 )1152 result, dom = run_and_parse(f, f)1153 result.stdout.no_fnmatch_line("*INTERNALERROR*")1154 first, second = [x["classname"] for x in dom.find_by_tag("testcase")]1155 assert first == second1156def test_runs_twice_xdist(testdir, run_and_parse):1157 pytest.importorskip("xdist")1158 testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD")1159 f = testdir.makepyfile(1160 """1161 def test_pass():1162 pass1163 """1164 )1165 result, dom = run_and_parse(f, "--dist", "each", "--tx", "2*popen")1166 result.stdout.no_fnmatch_line("*INTERNALERROR*")1167 first, second = [x["classname"] for x in dom.find_by_tag("testcase")]1168 assert first == second1169def test_fancy_items_regression(testdir, run_and_parse):1170 # issue 12591171 testdir.makeconftest(1172 """1173 import pytest1174 class FunItem(pytest.Item):1175 def runtest(self):1176 pass1177 class NoFunItem(pytest.Item):1178 def runtest(self):1179 pass1180 class FunCollector(pytest.File):1181 def collect(self):1182 return [1183 FunItem.from_parent(name='a', parent=self),1184 NoFunItem.from_parent(name='a', parent=self),1185 NoFunItem.from_parent(name='b', parent=self),1186 ]1187 def pytest_collect_file(path, parent):1188 if path.check(ext='.py'):1189 return FunCollector.from_parent(fspath=path, parent=parent)1190 """1191 )1192 testdir.makepyfile(1193 """1194 def test_pass():1195 pass1196 """1197 )1198 result, dom = run_and_parse()1199 result.stdout.no_fnmatch_line("*INTERNALERROR*")1200 items = sorted("%(classname)s %(name)s" % x for x in dom.find_by_tag("testcase"))1201 import pprint1202 pprint.pprint(items)1203 assert items == [1204 "conftest a",1205 "conftest a",1206 "conftest b",1207 "test_fancy_items_regression a",1208 "test_fancy_items_regression a",1209 "test_fancy_items_regression b",1210 "test_fancy_items_regression test_pass",1211 ]1212@parametrize_families1213def test_global_properties(testdir, xunit_family) -> None:1214 path = testdir.tmpdir.join("test_global_properties.xml")1215 log = LogXML(str(path), None, family=xunit_family)1216 class Report(BaseReport):1217 sections = [] # type: List[Tuple[str, str]]1218 nodeid = "test_node_id"1219 log.pytest_sessionstart()1220 log.add_global_property("foo", "1")1221 log.add_global_property("bar", "2")1222 log.pytest_sessionfinish()1223 dom = minidom.parse(str(path))1224 properties = dom.getElementsByTagName("properties")1225 assert properties.length == 1, "There must be one <properties> node"1226 property_list = dom.getElementsByTagName("property")1227 assert property_list.length == 2, "There most be only 2 property nodes"1228 expected = {"foo": "1", "bar": "2"}1229 actual = {}1230 for p in property_list:1231 k = str(p.getAttribute("name"))1232 v = str(p.getAttribute("value"))1233 actual[k] = v1234 assert actual == expected1235def test_url_property(testdir) -> None:1236 test_url = "http://www.github.com/pytest-dev"1237 path = testdir.tmpdir.join("test_url_property.xml")1238 log = LogXML(str(path), None)1239 class Report(BaseReport):1240 longrepr = "FooBarBaz"1241 sections = [] # type: List[Tuple[str, str]]1242 nodeid = "something"1243 location = "tests/filename.py", 42, "TestClass.method"1244 url = test_url1245 test_report = cast(TestReport, Report())1246 log.pytest_sessionstart()1247 node_reporter = log._opentestcase(test_report)1248 node_reporter.append_failure(test_report)1249 log.pytest_sessionfinish()1250 test_case = minidom.parse(str(path)).getElementsByTagName("testcase")[0]1251 assert (1252 test_case.getAttribute("url") == test_url1253 ), "The URL did not get written to the xml"1254@parametrize_families1255def test_record_testsuite_property(testdir, run_and_parse, xunit_family):1256 testdir.makepyfile(1257 """1258 def test_func1(record_testsuite_property):1259 record_testsuite_property("stats", "all good")1260 def test_func2(record_testsuite_property):1261 record_testsuite_property("stats", 10)1262 """1263 )1264 result, dom = run_and_parse(family=xunit_family)1265 assert result.ret == 01266 node = dom.find_first_by_tag("testsuite")1267 properties_node = node.find_first_by_tag("properties")1268 p1_node = properties_node.find_nth_by_tag("property", 0)1269 p2_node = properties_node.find_nth_by_tag("property", 1)1270 p1_node.assert_attr(name="stats", value="all good")1271 p2_node.assert_attr(name="stats", value="10")1272def test_record_testsuite_property_junit_disabled(testdir):1273 testdir.makepyfile(1274 """1275 def test_func1(record_testsuite_property):1276 record_testsuite_property("stats", "all good")1277 """1278 )1279 result = testdir.runpytest()1280 assert result.ret == 01281@pytest.mark.parametrize("junit", [True, False])1282def test_record_testsuite_property_type_checking(testdir, junit):1283 testdir.makepyfile(1284 """1285 def test_func1(record_testsuite_property):1286 record_testsuite_property(1, 2)1287 """1288 )1289 args = ("--junitxml=tests.xml",) if junit else ()1290 result = testdir.runpytest(*args)1291 assert result.ret == 11292 result.stdout.fnmatch_lines(1293 ["*TypeError: name parameter needs to be a string, but int given"]1294 )1295@pytest.mark.parametrize("suite_name", ["my_suite", ""])1296@parametrize_families1297def test_set_suite_name(testdir, suite_name, run_and_parse, xunit_family):1298 if suite_name:1299 testdir.makeini(1300 """1301 [pytest]1302 junit_suite_name={suite_name}1303 junit_family={family}1304 """.format(1305 suite_name=suite_name, family=xunit_family1306 )1307 )1308 expected = suite_name1309 else:1310 expected = "pytest"1311 testdir.makepyfile(1312 """1313 import pytest1314 def test_func():1315 pass1316 """1317 )1318 result, dom = run_and_parse(family=xunit_family)1319 assert result.ret == 01320 node = dom.find_first_by_tag("testsuite")1321 node.assert_attr(name=expected)1322def test_escaped_skipreason_issue3533(testdir, run_and_parse):1323 testdir.makepyfile(1324 """1325 import pytest1326 @pytest.mark.skip(reason='1 <> 2')1327 def test_skip():1328 pass1329 """1330 )1331 _, dom = run_and_parse()1332 node = dom.find_first_by_tag("testcase")1333 snode = node.find_first_by_tag("skipped")1334 assert "1 <> 2" in snode.text1335 snode.assert_attr(message="1 <> 2")1336@parametrize_families1337def test_logging_passing_tests_disabled_does_not_log_test_output(1338 testdir, run_and_parse, xunit_family1339):1340 testdir.makeini(1341 """1342 [pytest]1343 junit_log_passing_tests=False1344 junit_logging=system-out1345 junit_family={family}1346 """.format(1347 family=xunit_family1348 )1349 )1350 testdir.makepyfile(1351 """1352 import pytest1353 import logging1354 import sys1355 def test_func():1356 sys.stdout.write('This is stdout')1357 sys.stderr.write('This is stderr')1358 logging.warning('hello')1359 """1360 )1361 result, dom = run_and_parse(family=xunit_family)1362 assert result.ret == 01363 node = dom.find_first_by_tag("testcase")1364 assert len(node.find_by_tag("system-err")) == 01365 assert len(node.find_by_tag("system-out")) == 01366@parametrize_families1367@pytest.mark.parametrize("junit_logging", ["no", "system-out", "system-err"])1368def test_logging_passing_tests_disabled_logs_output_for_failing_test_issue5430(1369 testdir, junit_logging, run_and_parse, xunit_family1370):1371 testdir.makeini(1372 """1373 [pytest]1374 junit_log_passing_tests=False1375 junit_family={family}1376 """.format(1377 family=xunit_family1378 )1379 )1380 testdir.makepyfile(1381 """1382 import pytest1383 import logging1384 import sys1385 def test_func():1386 logging.warning('hello')1387 assert 01388 """1389 )1390 result, dom = run_and_parse(1391 "-o", "junit_logging=%s" % junit_logging, family=xunit_family1392 )1393 assert result.ret == 11394 node = dom.find_first_by_tag("testcase")1395 if junit_logging == "system-out":1396 assert len(node.find_by_tag("system-err")) == 01397 assert len(node.find_by_tag("system-out")) == 11398 elif junit_logging == "system-err":1399 assert len(node.find_by_tag("system-err")) == 11400 assert len(node.find_by_tag("system-out")) == 01401 else:1402 assert junit_logging == "no"1403 assert len(node.find_by_tag("system-err")) == 01404 assert len(node.find_by_tag("system-out")) == 0
Looking for an in-depth tutorial around pytest? LambdaTest covers the detailed pytest tutorial that has everything related to the pytest, from setting up the pytest framework to automation testing. Delve deeper into pytest testing by exploring advanced use cases like parallel testing, pytest fixtures, parameterization, executing multiple test cases from a single file, and more.
Skim our below pytest tutorial playlist to get started with automation testing using the pytest framework.
https://www.youtube.com/playlist?list=PLZMWkkQEwOPlcGgDmHl8KkXKeLF83XlrP
Get 100 minutes of automation test minutes FREE!!