Exemplo n.º 1
0
 def __init__(self, mode, app):
     self.mode = mode
     self.app = app
     self.server = Server(
         app).boot() if app and self.driver.needs_server else None
     self.synchronized = False
     self._scopes = [None]
Exemplo n.º 2
0
    def test_binds_to_the_specified_port(self, app):
        capybara.server_host = "127.0.0.1"
        server = Server(app).boot()
        with closing(urlopen("http://127.0.0.1:{}".format(
                server.port))) as response:
            assert "Hello Server" in decode_bytes(response.read())

        capybara.server_host = "0.0.0.0"
        server = Server(app).boot()
        with closing(urlopen("http://127.0.0.1:{}".format(
                server.port))) as response:
            assert "Hello Server" in decode_bytes(response.read())
Exemplo n.º 3
0
    def test_uses_the_existing_server_if_it_is_already_running(self, app):
        server1 = Server(app).boot()
        server2 = Server(app).boot()

        with closing(urlopen("http://{}:{}".format(
                server1.host, server1.port))) as response1:
            assert "Hello Server" in decode_bytes(response1.read())

        with closing(urlopen("http://{}:{}".format(
                server2.host, server2.port))) as response2:
            assert "Hello Server" in decode_bytes(response2.read())

        assert server1.port == server2.port
Exemplo n.º 4
0
    def test_finds_an_available_port(self, app):
        def app2(environ, start_response):
            start_response("200 OK", [])
            return [encode_string("Hello Second Server!")]

        server1 = Server(app).boot()
        server2 = Server(app2).boot()

        with closing(urlopen("http://{}:{}".format(
                server1.host, server1.port))) as response1:
            assert "Hello Server" in decode_bytes(response1.read())

        with closing(urlopen("http://{}:{}".format(
                server2.host, server2.port))) as response2:
            assert "Hello Second Server" in decode_bytes(response2.read())
Exemplo n.º 5
0
    def test_waits_for_pending_requests(self):
        counter = Counter()

        def app(environ, start_response):
            with counter:
                sleep(0.2)
                start_response("200 OK", [])
                return [encode_string("Hello Server!")]

        server = Server(app).boot()

        # Start request, but don't wait for it to finish
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((server.host, server.port))
        s.send(encode_string("GET / HTTP/1.0\r\n\r\n"))
        sleep(0.1)

        assert counter.value == 1

        server.wait_for_pending_requests()

        assert counter.value == 0

        s.close()
Exemplo n.º 6
0
 def test_uses_given_port(self, app):
     server = Server(app, port=22790).boot()
     with closing(urlopen("http://{}:22790".format(
             server.host))) as response:
         assert "Hello Server" in decode_bytes(response.read())
Exemplo n.º 7
0
 def test_uses_specified_port(self, app):
     capybara.server_port = 22789
     server = Server(app).boot()
     with closing(urlopen("http://{}:22789".format(
             server.host))) as response:
         assert "Hello Server" in decode_bytes(response.read())
Exemplo n.º 8
0
 def test_does_nothing_when_no_app_given(self):
     Server(None).boot()
Exemplo n.º 9
0
 def test_spools_up_a_wsgi_server(self, app):
     server = Server(app).boot()
     with closing(urlopen("http://{}:{}".format(server.host,
                                                server.port))) as response:
         assert "Hello Server" in decode_bytes(response.read())
Exemplo n.º 10
0
class Session(SessionMatchersMixin, object):
    """
    The Session class represents a single user's interaction with the system. The Session can use
    any of the underlying drivers. A session can be initialized manually like this::

        session = Session("selenium", MyWSGIApp)

    The application given as the second argument is optional. When running Capybara against an
    external page, you might want to leave it out::

        session = Session("selenium")
        session.visit("http://www.google.com")

    Session provides a number of methods and properties for controlling the navigation of the page,
    such as :meth:`visit`, :attr:`current_path`, and so on. It also delegates a number of methods to
    a :class:`Document`, representing the current HTML document. This allows interaction::

        session.fill_in("q", value="Capybara")
        session.click_button("Search")
        assert session.has_text("Capybara")

    When using :mod:`capybara.dsl`, the Session is initialized automatically for you.

    Args:
        mode (str): The name of the driver to use.
        app (object): The WSGI-compliant app with which to interact.
    """

    def __init__(self, mode, app):
        self.mode = mode
        self.app = app
        self.server = Server(app).boot() if app and self.driver.needs_server else None
        self.synchronized = False
        self._scopes = [None]

    @cached_property
    def driver(self):
        """ driver.Base: The driver for the current session. """
        return capybara.drivers[self.mode](self.app)

    @cached_property
    def document(self):
        """ Document: The document for the current page. """
        return Document(self, self.driver)

    @property
    def current_scope(self):
        """ node.Base: The current node relative to which all interaction will be scoped. """
        scope = self._scopes[-1]
        if scope in [None, "frame"]:
            scope = self.document
        return scope

    @property
    def html(self):
        """ str: A snapshot of the DOM of the current document, as it looks right now. """
        return self.driver.html

    body = html
    """ Alias for :attr:`html`. """

    source = html
    """ Alias for :attr:`html`. """

    @property
    def current_path(self):
        """ str: Path of the current page, without any domain information. """

        if not self.current_url:
            return

        path = urlparse(self.current_url).path
        return path if path else None

    @property
    def current_host(self):
        """ str: Host of the current page. """

        if not self.current_url:
            return

        result = urlparse(self.current_url)
        scheme, netloc = result.scheme, result.netloc
        host = netloc.split(":")[0] if netloc else None
        return "{0}://{1}".format(scheme, host) if host else None

    @property
    def current_url(self):
        """ str: Fully qualified URL of the current page. """
        return self.driver.current_url

    def visit(self, visit_uri):
        """
        Navigate to the given URL. The URL can either be a relative URL or an absolute URL. The
        behavior of either depends on the driver. ::

            session.visit("/foo")
            session.visit("http://google.com")

        For drivers which can run against an external application, such as the Selenium driver,
        giving an absolute URL will navigate to that page. This allows testing applications running
        on remote servers. For these drivers, setting :data:`capybara.app_host` will make the
        remote server the default. For example::

            capybara.app_host = "http://google.com"
            session.visit("/")  # visits the Google homepage

        Args:
            visit_uri (str): The URL to navigate to.
        """

        self.raise_server_error()

        visit_uri = urlparse(visit_uri)

        if capybara.app_host:
            uri_base = urlparse(capybara.app_host)
        elif self.server:
            uri_base = urlparse("http://{}:{}".format(self.server.host, self.server.port))
        else:
            uri_base = None

        visit_uri = ParseResult(
            scheme=visit_uri.scheme or (uri_base.scheme if uri_base else ""),
            netloc=visit_uri.netloc or (uri_base.netloc if uri_base else ""),
            path=visit_uri.path,
            params=visit_uri.params,
            query=visit_uri.query,
            fragment=visit_uri.fragment)

        self.driver.visit(visit_uri.geturl())

    def refresh(self):
        """ Refresh the page. """
        self.raise_server_error()
        self.driver.refresh()

    def go_back(self):
        """ Move back a single entry in the browser's history. """
        self.driver.go_back()

    def go_forward(self):
        """ Move forward a single entry in the browser's history. """
        self.driver.go_forward()

    @contextmanager
    def scope(self, *args, **kwargs):
        """
        Executes the wrapped code within the context of a node. ``scope`` takes the same options
        as :meth:`find`. For the duration of the context, any command to Capybara will be handled
        as though it were scoped to the given element. ::

            with scope("xpath", "//div[@id='delivery-address']"):
                fill_in("Street", value="12 Main Street")

        Just as with :meth:`find`, if multiple elements match the selector given to ``scope``, an
        error will be raised, and just as with :meth:`find`, this behavior can be controlled
        through the ``match`` and ``exact`` options.

        It is possible to omit the first argument, in that case, the selector is assumed to be of
        the type set in :data:`capybara.default_selector`. ::

            with scope("div#delivery-address"):
                fill_in("Street", value="12 Main Street")

        Note that a lot of uses of ``scope`` can be replaced more succinctly with chaining::

            find("div#delivery-address").fill_in("Street", value="12 Main Street")

        Args:
            *args: Variable length argument list for the call to :meth:`find`.
            **kwargs: Arbitrary keywords arguments for the call to :meth:`find`.
        """

        new_scope = args[0] if isinstance(args[0], Base) else self.find(*args, **kwargs)
        self._scopes.append(new_scope)
        try:
            yield
        finally:
            self._scopes.pop()

    @contextmanager
    def fieldset(self, locator):
        """
        Execute the wrapped code within a specific fieldset given the id or legend of that
        fieldset.

        Args:
            locator (str): The id or legend of the fieldset.
        """

        with self.scope("fieldset", locator):
            yield

    @contextmanager
    def table(self, locator):
        """
        Execute the wrapped code within a specific table given the id or caption of that table.

        Args:
            locator (str): The id or caption of the table.
        """

        with self.scope("table", locator):
            yield

    @contextmanager
    def frame(self, locator=None, *args, **kwargs):
        """
        Execute the wrapped code within the given iframe using the given frame or frame name/id.
        May not be supported by all drivers.

        Args:
            locator (str | Element, optional): The name/id of the frame or the frame's element.
                Defaults to the only frame in the document.
        """

        self.switch_to_frame(self._find_frame(locator, *args, **kwargs))
        try:
            yield
        finally:
            self.switch_to_frame("parent")

    def _find_frame(self, locator=None, *args, **kwargs):
        if isinstance(locator, Element):
            return locator

        if isinstance(locator, Hashable) and locator in selectors:
            selector = locator
            return self.find(selector, *args, **kwargs)

        return self.find("frame", locator, **kwargs)

    @property
    def current_window(self):
        """ Window: The current window. """
        return Window(self, self.driver.current_window_handle)

    @property
    def windows(self):
        """
        Get all opened windows. The order of the windows in the returned list is not defined. The
        driver may sort windows by their creation time but it's not required.

        Returns:
            List[Window]: A list of all windows.
        """

        return [Window(self, window_handle) for window_handle in self.driver.window_handles]

    def open_new_window(self):
        """
        Open new window. The current window doesn't change as a result of this call. It should be
        switched explicitly.

        Returns:
            Window: The window that has been opened.
        """

        return self.window_opened_by(lambda: self.driver.open_new_window())

    def switch_to_frame(self, frame):
        """
        Switch to the given frame.

        If you use this method you are responsible for making sure you switch back to the parent
        frame when done in the frame changed to. :meth:`frame` is preferred over this method and
        should be used when possible. May not be supported by all drivers.

        Args:
            frame (Element | str): The iframe/frame element to switch to.
        """

        if isinstance(frame, Element):
            self.driver.switch_to_frame(frame)
            self._scopes.append("frame")
        elif frame == "parent":
            if self._scopes[-1] != "frame":
                raise ScopeError("`switch_to_frame(\"parent\")` cannot be called "
                                 "from inside a descendant frame's `scope` context.")
            self._scopes.pop()
            self.driver.switch_to_frame("parent")
        elif frame == "top":
            if "frame" in self._scopes:
                idx = self._scopes.index("frame")
                if any([scope not in ["frame", None] for scope in self._scopes[idx:]]):
                    raise ScopeError("`switch_to_frame(\"top\")` cannot be called "
                                     "from inside a descendant frame's `scope` context.")
                self._scopes = self._scopes[:idx]
                self.driver.switch_to_frame("top")
        else:
            raise ValueError(
                "You must provide a frame element, \"parent\", or \"top\" "
                "when calling switch_to_frame")

    def switch_to_window(self, window, wait=None):
        """
        If ``window`` is a lambda, it switches to the first window for which ``window`` returns a
        value other than False or None. If a window that matches can't be found, the window will be
        switched back and :exc:`WindowError` will be raised.

        Args:
            window (Window | lambda): The window that should be switched to, or a filtering lambda.
            wait (int | float, optional): The number of seconds to wait to find the window.

        Returns:
            Window: The new current window.

        Raises:
            ScopeError: If this method is invoked inside :meth:`scope, :meth:`frame`, or
                :meth:`window`.
            WindowError: If no window matches the given lambda.
        """

        if len(self._scopes) > 1:
            raise ScopeError(
                "`switch_to_window` is not supposed to be invoked from "
                "within `scope`s, `frame`s, or other `window`s.")

        if isinstance(window, Window):
            self.driver.switch_to_window(window.handle)
            return window
        else:
            @self.document.synchronize(errors=(WindowError,), wait=wait)
            def switch_and_get_matching_window():
                original_window_handle = self.driver.current_window_handle
                try:
                    for handle in self.driver.window_handles:
                        self.driver.switch_to_window(handle)
                        result = window()
                        if result:
                            return Window(self, handle)
                except Exception:
                    self.driver.switch_to_window(original_window_handle)
                    raise

                self.driver.switch_to_window(original_window_handle)
                raise WindowError("Could not find a window matching lambda")

            return switch_and_get_matching_window()

    @contextmanager
    def window(self, window):
        """
        This method does the following:

        1. Switches to the given window (it can be located by window instance/lambda/string).
        2. Executes the given block (within window located at previous step).
        3. Switches back (this step will be invoked even if exception happens at second step).

        Args:
            window (Window | lambda): The desired :class:`Window`, or a lambda that will be run in
                the context of each open window and returns ``True`` for the desired window.
        """

        original = self.current_window
        if window != original:
            self.switch_to_window(window)
        self._scopes.append(None)
        try:
            yield
        finally:
            self._scopes.pop()
            if original != window:
                self.switch_to_window(original)

    def window_opened_by(self, trigger_func, wait=None):
        """
        Get the window that has been opened by the passed lambda. It will wait for it to be opened
        (in the same way as other Capybara methods wait). It's better to use this method than
        ``windows[-1]`` `as order of windows isn't defined in some drivers`__.

        __ https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html#h_note_10

        Args:
            trigger_func (func): The function that should trigger the opening of a new window.
            wait (int | float, optional): Maximum wait time. Defaults to
                :data:`capybara.default_max_wait_time`.

        Returns:
            Window: The window that has been opened within the lambda.

        Raises:
            WindowError: If lambda passed to window hasn't opened window or opened more than one
                window.
        """

        old_handles = set(self.driver.window_handles)
        trigger_func()

        @self.document.synchronize(wait=wait, errors=(WindowError,))
        def get_new_window():
            opened_handles = set(self.driver.window_handles) - old_handles
            if len(opened_handles) != 1:
                raise WindowError("lambda passed to `window_opened_by` "
                                  "opened {0} windows instead of 1".format(len(opened_handles)))
            return Window(self, list(opened_handles)[0])

        return get_new_window()

    def execute_script(self, script, *args):
        """
        Execute the given script, not returning a result. This is useful for scripts that return
        complex objects, such as jQuery statements. ``execute_script`` should be used over
        :meth:`evaluate_script` whenever possible.

        Args:
            script (str): A string of JavaScript to execute.
            *args: Variable length argument list to pass to the executed JavaScript string.
        """

        args = [arg.base if isinstance(arg, Base) else arg for arg in args]
        self.driver.execute_script(script, *args)

    def evaluate_script(self, script, *args):
        """
        Evaluate the given JavaScript and return the result. Be careful when using this with
        scripts that return complex objects, such as jQuery statements. :meth:`execute_script`
        might be a better alternative.

        Args:
            script (str): A string of JavaScript to evaluate.
            *args: Variable length argument list to pass to the executed JavaScript string.

        Returns:
            object: The result of the evaluated JavaScript (may be driver specific).
        """

        args = [arg.base if isinstance(arg, Base) else arg for arg in args]
        result = self.driver.evaluate_script(script, *args)
        return self._wrap_element_script_result(result)

    def evaluate_async_script(self, script, *args):
        """
        Evaluate the given JavaScript and obtain the result from a callback function which will be
        passed as the last argument to the script.

        Args:
            script (str): A string of JavaScript to evaluate.
            *args: Variable length argument list to pass to the executed JavaScript string.

        Returns:
            object: The result of the evaluated JavaScript (may be driver specific).
        """

        args = [arg.base if isinstance(arg, Base) else arg for arg in args]
        result = self.driver.evaluate_async_script(script, *args)
        return self._wrap_element_script_result(result)

    @contextmanager
    def accept_alert(self, text=None, wait=None):
        """
        Execute the wrapped code, accepting an alert.

        Args:
            text (str | RegexObject, optional): Text to match against the text in the modal.
            wait (int | float, optional): Maximum time to wait for the modal to appear after
                executing the wrapped code.

        Raises:
            ModalNotFound: If a modal dialog hasn't been found.
        """

        wait = wait or capybara.default_max_wait_time
        with self.driver.accept_modal("alert", text=text, wait=wait):
            yield

    @contextmanager
    def accept_confirm(self, text=None, wait=None):
        """
        Execute the wrapped code, accepting a confirm.

        Args:
            text (str | RegexObject, optional): Text to match against the text in the modal.
            wait (int | float, optional): Maximum time to wait for the modal to appear after
                executing the wrapped code.

        Raises:
            ModalNotFound: If a modal dialog hasn't been found.
        """

        with self.driver.accept_modal("confirm", text=text, wait=wait):
            yield

    @contextmanager
    def dismiss_confirm(self, text=None, wait=None):
        """
        Execute the wrapped code, dismissing a confirm.

        Args:
            text (str | RegexObject, optional): Text to match against the text in the modal.
            wait (int | float, optional): Maximum time to wait for the modal to appear after
                executing the wrapped code.

        Raises:
            ModalNotFound: If a modal dialog hasn't been found.
        """

        with self.driver.dismiss_modal("confirm", text=text, wait=wait):
            yield

    @contextmanager
    def accept_prompt(self, text=None, response=None, wait=None):
        """
        Execute the wrapped code, accepting a prompt, optionally responding to the prompt.

        Args:
            text (str | RegexObject, optional): Text to match against the text in the modal.
            response (str, optional): Response to provide to the prompt.
            wait (int | float, optional): Maximum time to wait for the modal to appear after
                executing the wrapped code.

        Raises:
            ModalNotFound: If a modal dialog hasn't been found.
        """

        with self.driver.accept_modal("prompt", text=text, response=response, wait=wait):
            yield

    @contextmanager
    def dismiss_prompt(self, text=None, wait=None):
        """
        Execute the wrapped code, dismissing a prompt.

        Args:
            text (str | RegexObject, optional): Text to match against the text in the modal.
            wait (int | float, optional): Maximum time to wait for the modal to appear after
                executing the wrapped code.

        Raises:
            ModalNotFound: If a modal dialog hasn't been found.
        """

        with self.driver.dismiss_modal("prompt", text=text, wait=wait):
            yield

    def save_page(self, path=None):
        """
        Save a snapshot of the page.

        If invoked without arguments, it will save a file to :data:`capybara.save_path` and the
        file will be given a randomly generated filename. If invoked with a relative path, the path
        will be relative to :data:`capybara.save_path`.

        Args:
            path (str, optional): The path to where it should be saved.

        Returns:
            str: The path to which the file was saved.
        """

        path = _prepare_path(path, "html")

        with open(path, "wb") as f:
            f.write(encode_string(self.body))

        return path

    def save_screenshot(self, path=None, **kwargs):
        """
        Save a screenshot of the page.

        If invoked without arguments, it will save a file to :data:`capybara.save_path` and the
        file will be given a randomly generated filename. If invoked with a relative path, the path
        will be relative to :data:`capybara.save_path`.

        Args:
            path (str, optional): The path to where it should be saved.
            **kwargs: Arbitrary keywords arguments for the driver.

        Returns:
            str: The path to which the file was saved.
        """

        path = _prepare_path(path, "png")
        self.driver.save_screenshot(path, **kwargs)
        return path

    def reset(self):
        """
        Reset the session (i.e., remove cookies and navigate to a blank page).

        This method does not:
        * accept modal dialogs if they are present (the Selenium driver does, but others may not),
        * clear the browser cache/HTML 5 local storage/IndexedDB/Web SQL database/etc., or
        * modify the state of the driver/underlying browser in any other way

        as doing so would result in performance downsides and it's not needed to do everything
        from the list above for most apps.

        If you want to do anything from the list above on a general basis you can write a test
        teardown method.
        """

        self.driver.reset()
        if self.server:
            self.server.wait_for_pending_requests()
        self.raise_server_error()

    def raise_server_error(self):
        """ Raise errors encountered by the server. """
        if self.server and self.server.error:
            try:
                if capybara.raise_server_errors:
                    raise self.server.error
            finally:
                self.server.reset_error()

    cleanup = reset
    """ Alias for :meth:`reset`. """

    reset_session = reset
    """ Alias for :meth:`reset`. """

    def _wrap_element_script_result(self, arg):
        if isinstance(arg, list):
            return [self._wrap_element_script_result(e) for e in arg]
        elif isinstance(arg, dict):
            return {k: self._wrap_element_script_result(v) for k, v in iter(arg.items())}
        elif isinstance(arg, Node):
            return Element(self, arg, None, None)
        else:
            return arg
 def servers(self):
     return [Server(copy(app)).boot() for _ in range(2)]