Ejemplo n.º 1
0
    def __init__(self, socket=None, wait=True):
        """
        Spawns a node.js subprocess that listens on a TCP socket.
        A :class:`zombie.proxy.client.ZombieProxyClient` streams data to
        the server, which evaluates it as Javascript, passes it on to
        a zombie.js Browser object, and returns the results.

        :param socket: a (random, by default) filepath representing the
                       intended TCP socket location
        :param wait: when True, wait until the node.js subprocess is responsive
                    via the specified TCP socket.
        """
        socket = socket or '/tmp/zombie-%s.sock' % random.randint(0, 10000)

        self.socket = socket
        #
        # Spawn the node proxy server in a subprocess.
        # This is a simple socket server that listens for data,
        # evaluates it as Javascript, and passes the eval'ed
        # input to a Zombie.js Browser object.
        #
        args = ['env', 'node', proxy_path, self.socket]
        self.child = subprocess.Popen(
            args,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT
        )
        self.child.stdin.close()

        if wait:
            # Wait until we can ping the node.js server
            client = ZombieProxyClient(socket)
            retries = 30
            while True:
                retries -= 1
                if retries < 0:  # pragma: nocover
                    raise RuntimeError(
                        "The proxy server has not replied within 3 seconds."
                    )
                try:
                    assert client.ping() == 'pong'
                except (SocketError, AssertionError):
                    pass
                else:
                    break
                time.sleep(.1)

        #
        # Start a thread to monitor and redirect the
        # subprocess stdout and stderr to the console.
        #
        PipeWorker(self.child.stdout).start()
Ejemplo n.º 2
0
    def __init__(self, socket=None, wait=True):
        """
        Spawns a node.js subprocess that listens on a TCP socket.
        A :class:`zombie.proxy.client.ZombieProxyClient` streams data to
        the server, which evaluates it as Javascript, passes it on to
        a zombie.js Browser object, and returns the results.

        :param socket: a (random, by default) filepath representing the
                       intended TCP socket location
        :param wait: when True, wait until the node.js subprocess is responsive
                    via the specified TCP socket.
        """
        socket = socket or '/tmp/zombie-%s.sock' % random.randint(0, 10000)

        self.socket = socket

        # Kill the node process when finished
        atexit.register(__kill_node_processes__)

        #
        # Spawn the node proxy server in a subprocess.
        # This is a simple socket server that listens for data,
        # evaluates it as Javascript, and passes the eval'ed
        # input to a Zombie.js Browser object.
        #
        args = ['env', 'node', proxy_path, self.socket]
        self.child = subprocess.Popen(
            args,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT
        )
        self.child.stdin.close()
        PipeWorker(self.child.stdout).start()
        if wait:
            # Wait until we can ping the node.js server
            client = ZombieProxyClient(socket)
            retries = 30
            while True:
                retries -= 1
                if retries < 0:  # pragma: nocover
                    raise RuntimeError(
                        "The proxy server has not replied within 3 seconds."
                    )
                try:
                    assert client.ping() == 'pong'
                except (SocketError, AssertionError):
                    pass
                else:
                    break
                time.sleep(.1)
Ejemplo n.º 3
0
    def __init__(self, server=None):
        """
        Start a new Browser instance.

        :param server: an (optional) instance of
                       :class:`zombie.proxy.server.ZombieProxyServer`.
        """
        #
        # If a proxy server isn't specified, spawn one automatically.
        #
        if server is None:
            server = ZombieProxyServer()
        self.server = server
        self.client = ZombieProxyClient(server.socket)
Ejemplo n.º 4
0
 def __init__(self, server=None):
     #
     # If a proxy server isn't specified, spawn one automatically.
     #
     if server is None:
         server = ZombieProxyServer()
     self.server = server
     self.client = ZombieProxyClient(server.socket)
Ejemplo n.º 5
0
class Browser(BaseNode):
    """
    A Browser object, analogous to zombie.Browser.
    """

    def __init__(self, server=None):
        #
        # If a proxy server isn't specified, spawn one automatically.
        #
        if server is None:
            server = ZombieProxyServer()
        self.server = server
        self.client = ZombieProxyClient(server.socket)

    #
    # Document Content
    #
    def html(self, selector='html', context=None):
        """
        Returns the HTML content of the current document.

        :param selector: an optional string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`DOMNode`
        """
        return self._with_context('html', selector, context)

    def query(self, selector, context=None):
        """
        Evaluate a CSS selector against the document (or an optional context
        DOMNode) and return a single DOMNode object.

        :param selector: a string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`DOMNode`
        """
        return self._query(selector, context, all_=False)

    def queryAll(self, selector, context=None):
        """
        Evaluate a CSS selector against the document (or an optional context
        DOMNode) and return a list of DOMNode objects.

        :param selector: a string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`DOMNode`
        """
        return self._query(selector, context)

    def css(self, selector, context=None):
        """
        An alias for Browser.queryAll.

        :param selector: a string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`DOMNode`
        """
        return self.queryAll(selector, context)

    def text(self, selector, context=None):
        """
        Returns the text content of specific elements

        :param selector: a string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`DOMNode`
        """
        return self._with_context('text', selector, context)

    #
    # Navigation
    #
    def clickLink(self, selector):
        self.client.wait('clickLink', selector)

    @property
    def location(self):
        return self.client.json('browser.location.toString()')

    @location.setter
    def location(self, url):
        self.visit(url)

    @verb
    def visit(self, url):
        """
        A shortcut to load the document from the specified URL.
        """
        self.client.wait('visit', url)

    @verb
    def back(self):
        self.client.wait('back')

    #
    # Forms
    #
    @verb
    def fill(self, field, value):
        """
        Fill a specified form field in the current document.
        """
        self._fill(field, value)

    @verb
    def pressButton(self, selector):
        """
        Press a specific button (by innerText or CSS selector in the current
        document.
        """
        self.client.wait('pressButton', selector)
Ejemplo n.º 6
0
    def __init__(self, socket=None, wait=True):
        """
        Spawns a node.js subprocess that listens on a TCP socket.
        A :class:`zombie.proxy.client.ZombieProxyClient` streams data to
        the server, which evaluates it as Javascript, passes it on to
        a zombie.js Browser object, and returns the results.

        :param socket: a (random, by default) filepath representing the
                       intended TCP socket location, or (on Windows) a tuple
                       containing a host and a port name (defaults to
                       ("127.0.0.1", 40140).
        :param wait: when True, wait until the node.js subprocess is responsive
                    via the specified TCP socket.
        """
        # no file based unix socket on windows
        if sys.platform == "win32":
            socket = socket or ("127.0.0.1", 40140)
        else:
            socket = socket or '/tmp/zombie-%s.sock' % random.randint(0, 10000)

        self.socket = socket

        # Kill the node process when finished
        atexit.register(__kill_node_processes__)

        #
        # Spawn the node proxy server in a subprocess.
        # This is a simple socket server that listens for data,
        # evaluates it as Javascript, and passes the eval'ed
        # input to a Zombie.js Browser object.
        #
        if sys.platform == "win32":
            # no env on windows,
            # just make sure that node is on the path
            # also no Unix file socket on windows, thus
            # only the port from the INET socket given as argument
            args = ['node', proxy_path, str(self.socket[1])]
        else:
            args = ['env', 'node', proxy_path, self.socket]
        self.child = subprocess.Popen(args,
                                      stdin=subprocess.PIPE,
                                      stdout=subprocess.PIPE,
                                      stderr=subprocess.STDOUT)
        self.child.stdin.close()
        PipeWorker(self.child.stdout).start()
        if wait:
            # Wait until we can ping the node.js server
            client = ZombieProxyClient(socket)
            retries = 30
            while True:
                retries -= 1
                if retries < 0:  # pragma: nocover
                    raise RuntimeError(
                        "The proxy server has not replied within 3 seconds.")
                try:
                    assert client.ping() == 'pong'
                except (SocketError, AssertionError):
                    pass
                else:
                    break
                time.sleep(.1)
Ejemplo n.º 7
0
class Browser(BaseNode):
    """
    A Browser object, analogous to zombie.js' ``Browser``.
    """

    def __init__(self, server=None):
        """
        Start a new Browser instance.

        :param server: an (optional) instance of
                       :class:`zombie.proxy.server.ZombieProxyServer`.
        """
        #
        # If a proxy server isn't specified, spawn one automatically.
        #
        if server is None:
            server = ZombieProxyServer()
        self.server = server
        self.client = ZombieProxyClient(server.socket)

    #
    # Document Content
    #
    @property
    def body(self):
        """
        Returns a :class:`zombie.dom.DOMNode` representing the body element of
        the current document.
        """

        js = """
            ELEMENTS.push(browser.body);
            stream.end(JSON.stringify(ELEMENTS.length - 1));
        """

        #
        # Translate the reference into a DOMNode object which can be used to
        # make subsequent object/attribute lookups later.
        #
        decoded = self.decode(self.client.send(js))
        return DOMNode(decoded, self.client)

    def html(self, selector='html', context=None):
        """
        Returns the HTML content (string) of the current document.

        :param selector: an optional string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`zombie.dom.DOMNode`
        """
        return self._with_context('html', selector, context)

    def query(self, selector, context=None):
        """
        Evaluate a CSS selector against the document (or an optional context
        :class:`zombie.dom.DOMNode`) and return a single
        :class:`zombie.dom.DOMNode` object.

        :param selector: a string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`zombie.dom.DOMNode`
        """
        return self._node('query', selector, context)

    def queryAll(self, selector, context=None):
        """
        Evaluate a CSS selector against the document (or an optional context
        :class:`zombie.dom.DOMNode`) and return a list of
        :class:`zombie.dom.DOMNode` objects.

        :param selector: a string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`zombie.dom.DOMNode`
        """
        return self._nodes('queryAll', selector, context)

    def css(self, selector, context=None):
        """
        An alias for :class:`zombie.browser.Browser.queryAll`.

        :param selector: a string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`zombie.dom.DOMNode`
        """
        return self.queryAll(selector, context)

    def text(self, selector, context=None):
        """
        Returns the text content of specific elements.

        :param selector: a string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`zombie.dom.DOMNode`
        """
        return self._with_context('text', selector, context)

    #
    # Navigation
    #
    def clickLink(self, selector):
        """
        Clicks on a link. The first argument is the link text or CSS selector.

        :param selector: an optional string CSS selector
                        (http://zombie.labnotes.org/selectors) or inner text

        Returns the :class:`zombie.browser.Browser` to allow function chaining.
        """
        self.client.wait('clickLink', selector)
        return self

    @property
    def location(self):
        """
        Returns the location of the current document (same as
        ``window.location.toString()``).
        """
        return self.client.json('browser.location.toString()')

    @location.setter
    def location(self, url):
        """
        Changes document location, loading a new document if necessary (same as
        setting ``window.location``). This will also work if you just need to
        change the hash (Zombie.js will fire a hashchange event).
        """
        self.visit(url)

    def visit(self, url):
        """
        A shortcut to load the document from the specified URL.

        Returns the :class:`zombie.browser.Browser` to allow function chaining.
        """
        self.client.wait('visit', url)
        return self

    def back(self):
        """
        Navigate to the previous page in history.

        Returns the :class:`zombie.browser.Browser` to allow function chaining.
        """
        self.client.wait('back')
        return self

    def link(self, selector):
        """
        Finds and returns a link ``<a>`` element (:class:`zombie.dom.DOMNode`).
        You can use a CSS selector or find a link by its text contents (case
        sensitive, but ignores leading/trailing spaces).

        :param selector: an optional string CSS selector
                        (http://zombie.labnotes.org/selectors) or inner text
        """
        return self._node('link', selector, None)

    def reload(self):
        """
        Reloads the current page.

        Returns the :class:`zombie.browser.Browser` to allow function chaining.
        """
        self.client.wait('reload')
        return self

    @property
    def statusCode(self):
        """
        Returns the status code returned for this page request (200, 303, etc).
        """
        return self.client.json('browser.statusCode')

    @property
    def success(self):
        """
        Returns ``True`` if the status code is 2xx.
        """
        return self.client.json('browser.success')

    @property
    def redirected(self):
        """
        Returns ``True`` if the page request followed a redirect.
        """
        return self.client.json('browser.redirected')

    #
    # Forms
    #
    def fill(self, field, value):
        """
        Fill a specified form field in the current document.

        :param field: an instance of :class:`zombie.dom.DOMNode`
        :param value: any string value

        Returns the :class:`zombie.browser.Browser` to allow function chaining.
        """
        self._fill(field, value)
        return self

    def pressButton(self, selector):
        """
        Press a specific button (by innerText or CSS selector) in the current
        document.

        Returns the :class:`zombie.browser.Browser` to allow function chaining.
        """
        self.client.wait('pressButton', selector)
        return self

    #
    # Debugging
    #
    def dump(self):
        """
        Prints a debug string including zombie.js version, current URL,
        history, cookies, event loop, etc.  Useful for debugging and submitting
        error reports.
        """
        self.client.json('browser.dump()')

    @property
    def resources(self):
        """
        Returns a list of resources loaded by the browser, e.g.,
        ::
            [{
                'url': '...',
                'time': '...ms',
                'size': '...kb',
                'request': '...',
                'response': '...'
            }]
        """
        js = """
            var resources = browser.resources.map(
                function(r){
                    return {
                        'url': r.url,
                        'time': r.time + 'ms',
                        'size': r.size / 1024 + 'kb',
                        'request': r.request.toString(),
                        'response': r.request.toString()
                    }
                }
            );
            stream.end(JSON.stringify(resources))
        """
        return self.decode(self.client.send(js))

    def viewInBrowser(self):
        """
        Views the current document in a real Web browser. Uses the default
        system browser on OS X, BSD and Linux. Probably errors on Windows.
        """
        return self.client.send('browser.viewInBrowser()')  # pragma: nocover
Ejemplo n.º 8
0
class Browser(object):
    """
    A Browser object, analogous to zombie.js' ``Browser``.
    """
    def __init__(self, server=None):
        """
        Start a new Browser instance.

        :param server: an (optional) instance of
                       :class:`zombie.proxy.server.ZombieProxyServer`.
        """
        #
        # If a proxy server isn't specified, spawn one automatically.
        #
        if server is None:
            server = ZombieProxyServer()
        self.server = server
        self.client = ZombieProxyClient(server.socket)

    #
    # Forms
    #
    def fill(self, field, value):
        """
        Fill a specified form field in the current document.

        :param field: an instance of :class:`zombie.dom.DOMNode`
        :param value: any string value
        :return: self to allow function chaining.
        """
        self.client.nowait('browser.fill', (field, value))
        return self

    def pressButton(self, selector):
        """
        Press a specific button.

        :param selector: CSS selector or innerText
        :return: self to allow function chaining.
        """
        self.client.wait('browser.pressButton', selector)
        return self

    def check(self, selector):
        self.client.nowait('browser.check', (selector, ))
        return self

    def uncheck(self, selector):
        self.client.nowait('browser.uncheck', (selector, ))
        return self

    def select(self, selector, value):
        self.client.nowait('browser.select', (selector, value))
        return self

    def selectOption(self, selector):
        self.client.nowait('browser.selectOption', (selector, ))
        return self

    def unselect(self, selector, value):
        self.client.nowait('browser.unselect', (selector, value))
        return self

    def attach(self, selector, filename):
        self.client.nowait('browser.attach', (selector, filename))
        return self

    def choose(self, selector):
        self.client.nowait('browser.choose', (selector, ))
        return self

    #
    # query
    #
    def field(self, selector, context=None):
        element = self.client.create_element('browser.field',
                                             (selector, context))
        return DOMNode(element, self)

    #
    # Document Content
    #
    def load(self, html):
        """
        Loads raw html
        """
        self.client.wait('browser.load', html)

    @property
    def body(self):
        """
        Returns a :class:`zombie.dom.DOMNode` representing the body element of
        the current document.
        """
        element = self.client.create_element('browser.body')
        return DOMNode(element, self)

    def html(self, selector=None, context=None):
        """
        Returns the HTML content (string) of the current document.

        :param selector: an optional string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`zombie.dom.DOMNode`
        """
        return self.client.json('browser.html', (selector, context))

    def query(self, selector, context=None):
        """
        Evaluate a CSS selector against the document (or an optional context
        :class:`zombie.dom.DOMNode`) and return a single
        :class:`zombie.dom.DOMNode` object.

        :param selector: a string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`zombie.dom.DOMNode`
        """
        element = self.client.create_element('browser.query',
                                             (selector, context))
        return DOMNode.factory(element, self)

    def queryAll(self, selector, context=None):
        """
        Evaluate a CSS selector against the document (or an optional context
        :class:`zombie.dom.DOMNode`) and return a list of
        :class:`zombie.dom.DOMNode` objects.

        :param selector: a string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`zombie.dom.DOMNode`
        """
        elements = self.client.create_elements('browser.queryAll',
                                               (selector, context))
        return [DOMNode(e, self) for e in elements]

    def css(self, selector, context=None):
        """
        An alias for :class:`zombie.browser.Browser.queryAll`.

        :param selector: a string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`zombie.dom.DOMNode`
        """
        return self.queryAll(selector, context)

    def text(self, selector, context=None):
        """
        Returns the text content of specific elements.

        :param selector: a string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`zombie.dom.DOMNode`
        """
        return self.client.json('browser.text', (selector, context))

    def unselectOption(self, selector):
        """
        Unselects the given option

        Special case. It seems there is a problem in Zombie: unselectOption
        doesn't accept a selector.

        Pull request pending in the zombie project
        """
        self.query(selector).unselectOption()
        return self

    #
    # Navigation
    #
    def clickLink(self, selector):
        """
        Clicks on a link. The first argument is the link text or CSS selector.

        :param selector: an optional string CSS selector
                        (http://zombie.labnotes.org/selectors) or inner text

        Returns the :class:`zombie.browser.Browser` to allow function chaining.
        """
        self.client.wait('browser.clickLink', selector)
        return self

    @property
    def location(self):
        """
        Returns the location of the current document (same as
        ``window.location.toString()``).
        """
        return self.client.json('browser.location.toString()')

    @location.setter
    def location(self, url):
        """
        Changes document location, loading a new document if necessary (same as
        setting ``window.location``). This will also work if you just need to
        change the hash (Zombie.js will fire a hashchange event).
        """
        self.visit(url)

    def visit(self, url):
        """
        A shortcut to load the document from the specified URL.

        Returns the :class:`zombie.browser.Browser` to allow function chaining.
        """
        self.client.wait('browser.visit', url)
        return self

    def back(self):
        """
        Navigate to the previous page in history.

        Returns the :class:`zombie.browser.Browser` to allow function chaining.
        """
        self.client.wait('browser.back')
        return self

    def link(self, selector):
        """
        Finds and returns a link ``<a>`` element (:class:`zombie.dom.DOMNode`).
        You can use a CSS selector or find a link by its text contents (case
        sensitive, but ignores leading/trailing spaces).

        :param selector: an optional string CSS selector
                        (http://zombie.labnotes.org/selectors) or inner text
        """
        element = self.client.create_element('browser.link', (selector, ))
        return DOMNode(element, self)

    def reload(self):
        """
        Reloads the current page.

        Returns the :class:`zombie.browser.Browser` to allow function chaining.
        """
        self.client.wait('browser.reload')
        return self

    @property
    def statusCode(self):
        """
        Returns the status code returned for this page request (200, 303, etc).
        """
        return self.client.json('browser.statusCode')

    @property
    def success(self):
        """
        Returns ``True`` if the status code is 2xx.
        """
        return self.client.json('browser.success')

    @property
    def redirected(self):
        """
        Returns ``True`` if the page request followed a redirect.
        """
        return self.client.json('browser.redirected')

    def fire(self, selector, event_name):
        self.client.wait('browser.fire', selector, event_name)
        return self

    #
    # Debugging
    #
    def dump(self):
        """
        Prints a debug string including zombie.js version, current URL,
        history, cookies, event loop, etc.  Useful for debugging and submitting
        error reports.
        """
        self.client.json('browser.dump()')

    def get_resource(self, url):
        """
        Gets a resource and returns a json with information
        """
        return self.client.wait_return('browser.resources.get', url)

    def post_resource(self, url, form_params):
        options = {
            'headers': {
                'content-type': 'application/x-www-form-urlencoded'
            },
            'params': form_params
        }
        return self.client.wait_return('browser.resources.post', url, options)

    def evaluate(self, code):
        return self.client.json('browser.evaluate', (code, ))

    def wait(self, wait_argument=None):
        arguments = [] if wait_argument is None else [wait_argument]
        self.client.wait('browser.wait', *arguments)

    @property
    def resources(self):
        """
        Returns a list of resources loaded by the browser, e.g.,
        ::
            [{
                'method': 'GET',
                'url': 'http://www.example.com/',
                'statusCode': '200',
                'statusText': 'OK',
                'time': '200ms'
            }]
        """
        js = """
            browser.resources.map(
                function(r){
                    var request = r.request;
                    var response = r.response;
                    return {
                        'method': request.method,
                        'url': request.url,
                        'statusCode': response.statusCode,
                        'statusText': response.statusText,
                        'time': (response.time - request.time) + 'ms',
                    }
                }
            )
        """
        return self.client.json(js)

    def viewInBrowser(self):
        """
        Views the current document in a real Web browser. Uses the default
        system browser on OS X, BSD and Linux. Probably errors on Windows.
        """
        return self.client.send('browser.viewInBrowser()')  # pragma: nocover
Ejemplo n.º 9
0
class TestServerCommunication(TestCase):

    def setUp(self):
        super(TestServerCommunication, self).setUp()
        self.server = ZombieProxyServer()
        self.client = ZombieProxyClient(self.server.socket)

    def tearDown(self):
        super(TestServerCommunication, self).tearDown()
        fudge.clear_expectations()

    def test_encode(self):
        foo = {
            'foo': 'bar'
        }
        self.client.__encode__(foo) == dumps(foo)

        self.client.__encode__(FakeNode()) == 'ENCODED'

    def test_decode(self):
        foo = dumps({
            'foo': 'bar'
        })
        self.client.__decode__(foo) == loads(foo)

    def test_simple_send(self):
        self.client.send(
            "stream.end()"
        )

    def test_simple_json(self):
        obj = {
            'foo': 'bar',
            'test': 500
        }
        assert self.client.json(obj) == obj

    @fudge.with_fakes
    def test_simple_wait(self):

        js = """
        try {
            browser.visit("%s", function(err, browser){
                if (err)
                    stream.end(JSON.stringify(err.message));
                else
                    stream.end();
            });
        } catch (err) {
            stream.end(JSON.stringify(err.message));
        }
        """ % 'http://example.com'

        with fudge.patched_context(
            ZombieProxyClient,
            'send',
            (fudge.Fake('send', expect_call=True).
                with_args(js)
            )):

            self.client.wait('visit', 'http://example.com')

    @fudge.with_fakes
    def test_wait_without_arguments(self):

        js = """
        try {
            browser.wait(function(err, browser){
                if (err)
                    stream.end(JSON.stringify(err.message));
                else
                    stream.end();
            });
        } catch (err) {
            stream.end(JSON.stringify(err.message));
        }
        """

        with fudge.patched_context(
            ZombieProxyClient,
            'send',
            (fudge.Fake('send', expect_call=True).
                with_args(js)
            )):

            self.client.wait('wait')
Ejemplo n.º 10
0
 def setUp(self):
     super(TestServerCommunication, self).setUp()
     self.server = ZombieProxyServer()
     self.client = ZombieProxyClient(self.server.socket)
Ejemplo n.º 11
0
 def setUp(self):
     super(ZombieProxyClientTests, self).setUp()
     # Note, As a singleton so it will be created once, not in every test.
     self.server = ZombieProxyServer()
     self.client = ZombieProxyClient(self.server.socket)
Ejemplo n.º 12
0
class ZombieProxyClientTests(WebServerTestCase):
    def setUp(self):
        super(ZombieProxyClientTests, self).setUp()
        # Note, As a singleton so it will be created once, not in every test.
        self.server = ZombieProxyServer()
        self.client = ZombieProxyClient(self.server.socket)

    def tearDown(self):
        super(ZombieProxyClientTests, self).tearDown()

    def test_simple_json(self):
        obj = {
            'foo': 'bar',
            'test': 500
        }
        self.assertEqual(obj, self.client.json(obj))

    def test_malformed_command(self):
        with self.assertRaises(NodeError):
            self.client.json("banana")

    def test_nowait(self):
        self.assertEqual('Test', self.client.nowait("result = 'Test';"))

    def test_wait(self):
        self.client.wait('browser.visit', self.base_url)

    def test_wait_error(self):
        with self.assertRaises(NodeError):
            self.client.wait('browser.visit', self.base_url + 'notfound')

    def test_ping(self):
        self.assertEqual("pong", self.client.ping())

    def test_cleanup(self):
        client = self.client
        self.assertEqual(1, client.json('browser.testing = 1'))
        client.cleanup()
        self.assertFalse(client.json('"testing" in browser'))

    def test_create_element(self):
        client = self.client
        client.wait('browser.visit', self.base_url)
        self.assertEqual(
            0,
            client.create_element('browser.query', ('form',)).index)
        self.assertEqual(
            1,
            client.create_element('browser.query', ('form',)).index)

    def test_create_element_attribute(self):
        client = self.client
        client.wait('browser.visit', self.base_url)
        self.assertEqual(
            0, client.create_element('browser.html').index)

    def test_create_elements(self):
        client = self.client
        client.wait('browser.visit', self.base_url)
        res = client.create_elements('browser.queryAll', ('input', ))
        self.assertEqual(list(range(6)), [x.index for x in res])
Ejemplo n.º 13
0
 def setUp(self):
     super(TestServerCommunication, self).setUp()
     self.server = ZombieProxyServer()
     self.client = ZombieProxyClient(self.server.socket)
Ejemplo n.º 14
0
class TestServerCommunication(TestCase):
    def setUp(self):
        super(TestServerCommunication, self).setUp()
        self.server = ZombieProxyServer()
        self.client = ZombieProxyClient(self.server.socket)

    def tearDown(self):
        super(TestServerCommunication, self).tearDown()
        fudge.clear_expectations()

    def test_encode(self):
        foo = {'foo': 'bar'}
        self.client.__encode__(foo) == dumps(foo)

        self.client.__encode__(FakeNode()) == 'ENCODED'

    def test_decode(self):
        foo = dumps({'foo': 'bar'})
        self.client.__decode__(foo) == loads(foo)

    def test_simple_send(self):
        self.client.send("stream.end()")

    def test_simple_json(self):
        obj = {'foo': 'bar', 'test': 500}
        assert self.client.json(obj) == obj

    @fudge.with_fakes
    def test_simple_wait(self):

        js = """
        try {
            browser.visit("%s", function(err, browser){
                if (err)
                    stream.end(JSON.stringify(err.stack));
                else
                    stream.end();
            });
        } catch (err) {
            stream.end(JSON.stringify(err.stack));
        }
        """ % 'http://example.com'

        with fudge.patched_context(
                ZombieProxyClient, 'send',
            (fudge.Fake('send', expect_call=True).with_args(js))):
            self.client.wait('visit', 'http://example.com')

    @fudge.with_fakes
    def test_wait_without_arguments(self):

        js = """
        try {
            browser.wait(function(err, browser){
                if (err)
                    stream.end(JSON.stringify(err.stack));
                else
                    stream.end();
            });
        } catch (err) {
            stream.end(JSON.stringify(err.stack));
        }
        """

        with fudge.patched_context(
                ZombieProxyClient, 'send',
            (fudge.Fake('send', expect_call=True).with_args(js))):
            self.client.wait('wait')
Ejemplo n.º 15
0
class Browser(object):
    """
    A Browser object, analogous to zombie.js' ``Browser``.
    """

    def __init__(self, server=None):
        """
        Start a new Browser instance.

        :param server: an (optional) instance of
                       :class:`zombie.proxy.server.ZombieProxyServer`.
        """
        #
        # If a proxy server isn't specified, spawn one automatically.
        #
        if server is None:
            server = ZombieProxyServer()
        self.server = server
        self.client = ZombieProxyClient(server.socket)

    #
    # Forms
    #
    def fill(self, field, value):
        """
        Fill a specified form field in the current document.

        :param field: an instance of :class:`zombie.dom.DOMNode`
        :param value: any string value
        :return: self to allow function chaining.
        """
        self.client.nowait('browser.fill', (field, value))
        return self

    def pressButton(self, selector):
        """
        Press a specific button.

        :param selector: CSS selector or innerText
        :return: self to allow function chaining.
        """
        self.client.wait('browser.pressButton', selector)
        return self

    def check(self, selector):
        self.client.nowait('browser.check', (selector,))
        return self

    def uncheck(self, selector):
        self.client.nowait('browser.uncheck', (selector,))
        return self

    def select(self, selector, value):
        self.client.nowait('browser.select', (selector, value))
        return self

    def selectOption(self, selector):
        self.client.nowait('browser.selectOption', (selector,))
        return self

    def unselect(self, selector, value):
        self.client.nowait('browser.unselect', (selector, value))
        return self

    def attach(self, selector, filename):
        self.client.nowait('browser.attach', (selector, filename))
        return self

    def choose(self, selector):
        self.client.nowait('browser.choose', (selector, ))
        return self

    #
    # query
    #
    def field(self, selector, context=None):
        element = self.client.create_element(
            'browser.field', (selector, context))
        return DOMNode(element, self)

    #
    # Document Content
    #
    def load(self, html):
        """
        Loads raw html
        """
        self.client.wait('browser.load', html)

    @property
    def body(self):
        """
        Returns a :class:`zombie.dom.DOMNode` representing the body element of
        the current document.
        """
        element = self.client.create_element('browser.body')
        return DOMNode(element, self)

    def html(self, selector=None, context=None):
        """
        Returns the HTML content (string) of the current document.

        :param selector: an optional string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`zombie.dom.DOMNode`
        """
        return self.client.json('browser.html', (selector, context))

    def query(self, selector, context=None):
        """
        Evaluate a CSS selector against the document (or an optional context
        :class:`zombie.dom.DOMNode`) and return a single
        :class:`zombie.dom.DOMNode` object.

        :param selector: a string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`zombie.dom.DOMNode`
        """
        element = self.client.create_element(
            'browser.query', (selector, context))
        return DOMNode.factory(element, self)

    def queryAll(self, selector, context=None):
        """
        Evaluate a CSS selector against the document (or an optional context
        :class:`zombie.dom.DOMNode`) and return a list of
        :class:`zombie.dom.DOMNode` objects.

        :param selector: a string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`zombie.dom.DOMNode`
        """
        elements = self.client.create_elements(
            'browser.queryAll', (selector, context))
        return [DOMNode(e, self) for e in elements]

    def css(self, selector, context=None):
        """
        An alias for :class:`zombie.browser.Browser.queryAll`.

        :param selector: a string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`zombie.dom.DOMNode`
        """
        return self.queryAll(selector, context)

    def text(self, selector, context=None):
        """
        Returns the text content of specific elements.

        :param selector: a string CSS selector
                        (http://zombie.labnotes.org/selectors)
        :param context: an (optional) instance of :class:`zombie.dom.DOMNode`
        """
        return self.client.json('browser.text', (selector, context))

    def unselectOption(self, selector):
        """
        Unselects the given option

        Special case. It seems there is a problem in Zombie: unselectOption
        doesn't accept a selector.

        Pull request pending in the zombie project
        """
        self.query(selector).unselectOption()
        return self

    #
    # Navigation
    #
    def clickLink(self, selector):
        """
        Clicks on a link. The first argument is the link text or CSS selector.

        :param selector: an optional string CSS selector
                        (http://zombie.labnotes.org/selectors) or inner text

        Returns the :class:`zombie.browser.Browser` to allow function chaining.
        """
        self.client.wait('browser.clickLink', selector)
        return self

    @property
    def location(self):
        """
        Returns the location of the current document (same as
        ``window.location.toString()``).
        """
        return self.client.json('browser.location.toString()')

    @location.setter
    def location(self, url):
        """
        Changes document location, loading a new document if necessary (same as
        setting ``window.location``). This will also work if you just need to
        change the hash (Zombie.js will fire a hashchange event).
        """
        self.visit(url)

    def visit(self, url):
        """
        A shortcut to load the document from the specified URL.

        Returns the :class:`zombie.browser.Browser` to allow function chaining.
        """
        self.client.wait('browser.visit', url)
        return self

    def back(self):
        """
        Navigate to the previous page in history.

        Returns the :class:`zombie.browser.Browser` to allow function chaining.
        """
        self.client.wait('browser.back')
        return self

    def link(self, selector):
        """
        Finds and returns a link ``<a>`` element (:class:`zombie.dom.DOMNode`).
        You can use a CSS selector or find a link by its text contents (case
        sensitive, but ignores leading/trailing spaces).

        :param selector: an optional string CSS selector
                        (http://zombie.labnotes.org/selectors) or inner text
        """
        element = self.client.create_element('browser.link', (selector,))
        return DOMNode(element, self)

    def reload(self):
        """
        Reloads the current page.

        Returns the :class:`zombie.browser.Browser` to allow function chaining.
        """
        self.client.wait('browser.reload')
        return self

    @property
    def statusCode(self):
        """
        Returns the status code returned for this page request (200, 303, etc).
        """
        return self.client.json('browser.statusCode')

    @property
    def success(self):
        """
        Returns ``True`` if the status code is 2xx.
        """
        return self.client.json('browser.success')

    @property
    def redirected(self):
        """
        Returns ``True`` if the page request followed a redirect.
        """
        return self.client.json('browser.redirected')

    def fire(self, selector, event_name):
        self.client.wait('browser.fire', selector, event_name)
        return self

    #
    # Debugging
    #
    def dump(self):
        """
        Prints a debug string including zombie.js version, current URL,
        history, cookies, event loop, etc.  Useful for debugging and submitting
        error reports.
        """
        self.client.json('browser.dump()')

    def get_resource(self, url):
        """
        Gets a resource and returns a json with information
        """
        return self.client.wait_return('browser.resources.get', url)

    def post_resource(self, url, form_params):
        options = {
            'headers': {
                'content-type': 'application/x-www-form-urlencoded'},
            'params': form_params}
        return self.client.wait_return('browser.resources.post', url, options)

    def evaluate(self, code):
        return self.client.json('browser.evaluate', (code, ))

    def wait(self, wait_argument=None):
        arguments = [] if wait_argument is None else [wait_argument]
        self.client.wait('browser.wait', *arguments)

    @property
    def resources(self):
        """
        Returns a list of resources loaded by the browser, e.g.,
        ::
            [{
                'method': 'GET',
                'url': 'http://www.example.com/',
                'statusCode': '200',
                'statusText': 'OK',
                'time': '200ms'
            }]
        """
        js = """
            browser.resources.map(
                function(r){
                    var request = r.request;
                    var response = r.response;
                    return {
                        'method': request.method,
                        'url': request.url,
                        'statusCode': response.statusCode,
                        'statusText': response.statusText,
                        'time': (response.time - request.time) + 'ms',
                    }
                }
            )
        """
        return self.client.json(js)

    def viewInBrowser(self):
        """
        Views the current document in a real Web browser. Uses the default
        system browser on OS X, BSD and Linux. Probably errors on Windows.
        """
        return self.client.send('browser.viewInBrowser()')  # pragma: nocover