Weasel-0.32000755001750001750 015013700014 11714 5ustar00erikerik000000000000README100644001750001750 61015013700014 12632 0ustar00erikerik000000000000Weasel-0.32This archive contains the distribution Weasel, version 0.32: PHP's Mink inspired multi-protocol web-testing library for Perl This software is copyright (c) 2025 by Erik Huelsmann. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. This README file was generated by Dist::Zilla::Plugin::Readme v6.033. LICENSE100644001750001750 4643515013700014 13036 0ustar00erikerik000000000000Weasel-0.32This software is copyright (c) 2025 by Erik Huelsmann. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Terms of the Perl programming language system itself a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License" --- The GNU General Public License, Version 1, February 1989 --- This software is Copyright (c) 2025 by Erik Huelsmann. This is free software, licensed under: The GNU General Public License, Version 1, February 1989 GNU GENERAL PUBLIC LICENSE Version 1, February 1989 Copyright (C) 1989 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The license agreements of most software companies try to keep users at the mercy of those companies. By contrast, our General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. The General Public License applies to the Free Software Foundation's software and to any other program whose authors commit to using it. You can use it for your programs, too. When we speak of free software, we are referring to freedom, not price. Specifically, the General Public License is designed to make sure that you have the freedom to give away or sell copies of free software, that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of a such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must tell them their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any work containing the Program or a portion of it, either verbatim or with modifications. Each licensee is addressed as "you". 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this General Public License and to the absence of any warranty; and give any other recipients of the Program a copy of this General Public License along with the Program. You may charge a fee for the physical act of transferring a copy. 2. You may modify your copy or copies of the Program or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains the Program or any part thereof, either with or without modifications, to be licensed at no charge to all third parties under the terms of this General Public License (except that you may choose to grant warranty protection to some or all third parties, at your option). c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the simplest and most usual way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this General Public License. d) You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another independent work with the Program (or its derivative) on a volume of a storage or distribution medium does not bring the other work under the scope of these terms. 3. You may copy and distribute the Program (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal charge for the cost of distribution) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) Source code for a work means the preferred form of the work for making modifications to it. For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs, or for standard header files or definitions files that accompany that operating system. 4. You may not copy, modify, sublicense, distribute or transfer the Program except as expressly provided under this General Public License. Any attempt otherwise to copy, modify, sublicense, distribute or transfer the Program is void, and will automatically terminate your rights to use the Program under this License. However, parties who have received copies, or rights to use copies, from you under this General Public License will not have their licenses terminated so long as such parties remain in full compliance. 5. By copying, distributing or modifying the Program (or any work based on the Program) you indicate your acceptance of this license to do so, and all its terms and conditions. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. 7. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of the license which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the license, you may choose any version ever published by the Free Software Foundation. 8. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to humanity, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19xx name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (a program to direct compilers to make passes at assemblers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice That's all there is to it! --- The Perl Artistic License 1.0 --- This software is Copyright (c) 2025 by Erik Huelsmann. This is free software, licensed under: The Perl Artistic License 1.0 The "Artistic License" Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder as specified below. "Copyright Holder" is whoever is named in the copyright or copyrights for the package. "You" is you, if you're thinking about copying or distributing this Package. "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as uunet.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) give non-standard executables non-standard names, and clearly document the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. You may embed this Package's interpreter within an executable of yours (by linking); this shall be construed as a mere form of aggregation, provided that the complete Standard Version of the interpreter is so embedded. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whoever generated them, and may be sold commercially, and may be aggregated with this Package. If such scripts or library files are aggregated with this Package via the so-called "undump" or "unexec" methods of producing a binary executable image, then distribution of such an image shall neither be construed as a distribution of this Package nor shall it fall under the restrictions of Paragraphs 3 and 4, provided that you do not represent such an executable image as a Standard Version of this Package. 7. C subroutines (or comparably compiled subroutines in other languages) supplied by you and linked into this Package in order to emulate subroutines and variables of the language defined by this Package shall not be considered part of this Package, but are the equivalent of input as in Paragraph 6, provided these subroutines do not change the language in any way that would cause it to fail the regression tests for the language. 8. Aggregation of this Package with a commercial distribution is always permitted provided that the use of this Package is embedded; that is, when no overt attempt is made to make this Package's interfaces visible to the end user of the commercial distribution. Such use shall not be construed as a distribution of this Package. 9. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End CHANGES100644001750001750 1056015013700014 13012 0ustar00erikerik000000000000Weasel-0.32 ** 0.32 / 2025-05-22 - Change minimum Perl version to 5.12.0 - Change logging in 'find_all' to show better debug information on the pattern actually being searched for - Add lots of documentation ** 0.31 / 2023-07-24 - Fix typo in 'titled' expander affecting 'field' as well - Update/expand POD ** 0.30 / 2023-07-24 - Add 'titled' and 'field' expanders - Fix POD failures in Weasel::FindExpanders::HTML ** 0.29 / 2021-06-09 - Update version number everywhere instead of just in dist.ini ** 0.28 / 2021-06-09 - Use a javascript workaround to get textarea value to fix Firefox problem ** 0.27 / 2020-12-19 - Rely on driver to get input checkbox & radio selected state ** 0.26 / 2019-08-14 - Add 'on_timeout' callback to 'wait_for' driver API ** 0.25 / 2019-07-07 - Suppress 'uninitialized variable' warning in "tag_name" Weasel::Session subroutine. ** 0.24 / 2019-06-25 - Add attribute tracking session usage to Weasel::Session ('state') ** 0.23 / 2019-06-21 - Mark multiple methods DEPRECATED (up for removal on 1.0 or in 2 years -- whichever comes first) - Factor radio buttons into a separate widget: the value depends on the selected element in the group; not on the button itself - Add option to find radio buttons by their VALUE attribute's value - Don't call 'set_attribute' on INPUT widgets (instead, send keys) - Add 'value'/'values' distinction to SELECT widgets (multi-value support) - Add 'set_attribute' complement of 'get_attribute' ** 0.22 / 2019-02-28 - Correctly update $VERSION strings (incorrectly updated on 0.21) ** 0.21 / 2019-02-27 - Suppress output of second and later 'wait_for' iterations in order to increase the usefulness of the log content (Note that the logger now does include the number of retries) - Implement three missing element interactions in Weasel::Session, the broker component linking the driver to the front-end (element) API: set_attribute, get_selected & set_selected ** 0.20 / 2019-02-20 - Don't send driver interaction caused by logging to the logger ** 0.19 / 2019-02-17 - Fix bug in logging callback handling failing to use the $post coderef ** 0.18 / 2019-02-11 - Revert use of CSS locators for find and find_all; leaving the XPath compiler dependency in place for use with FindExpanders later ** 0.17 / 2019-02-11 - Use Weasel::Widgets::HTML::Input as a wrapper for TEXTAREAs - Support CSS locators for Weasel::Element->find and ->find_all (by adding dependency on HTML::Selector::XPath) ** 0.16 / 2019-02-10 - Allow passing constructor arguments to widgets being constructed as a result from a 'find' or 'find_all' action ** 0.15 / 2018-09-16 - Explicitly declare dependency on namespace::autoclean ** 0.14 / 2018-09-11 - Correctly update version numbers in all files ** 0.13 / 2018-09-10 - Normalize space in tag content (text()) being compared Removes the problem of leading and trailing space preventing matches ** 0.12 / 2017-07-12 - Make 'get_page_source' write to file ** 0.11 / 2017-04-21 - Fix expansion of *text HTML expander when only one of 'id' or 'name' given ** 0.10 / 2016-09-09 - Repair <5.14 compatibility - Add new 'get_page_source' api to the driver - Add driver API version check during session creation ** 0.09 / 2016-09-04 - Set the correct version number in Weasel.pm ** 0.08 / 2016-08-26 - Add the possibility to use environment variables for base_url ** 0.07 / 2016-08-20 - Correctly update the version numbers in lib/Weasel.pm ** 0.06 / 2016-08-20 - Add key codes in order to send special keys to elements - Add helper method on elements: has_class - Move development time tests to 'xt/' directory (fixes #1) ** 0.05 / 2016-07-08 - Change behaviour of Selectable widget (Radiobutton, Checkbox and Option) to return the empty string (false) when not selected for the 'value' attribute ** 0.04 / 2016-07-04 - Fix timing problem initializing Session's 'page' attribute (depends on 'page_class') by making it lazy ** 0.03 / 2016-07-03 - Allow overriding the class of the object instantiated into the 'page' attribute ** 0.02 / 2016-06-22 (TRIAL) - Adjusted dependencies based on testing in clean VM ** 0.01 / 2016-06-22 (TRIAL) - Initial release to replace LedgerSMB's (https://github.com/ledgersmb/LedgerSMB) test code META.yml100644001750001750 420215013700014 13244 0ustar00erikerik000000000000Weasel-0.32--- abstract: "PHP's Mink inspired multi-protocol web-testing library for Perl" author: - 'Erik Huelsmann ' build_requires: Test::More: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 generated_by: 'Dist::Zilla version 6.033, CPAN::Meta::Converter version 2.150010' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Weasel provides: Weasel: file: lib/Weasel.pm version: '0.32' Weasel::Element: file: lib/Weasel/Element.pm version: '0.32' Weasel::Element::Document: file: lib/Weasel/Element/Document.pm version: '0.32' Weasel::FindExpanders: file: lib/Weasel/FindExpanders.pm version: '0.32' Weasel::FindExpanders::HTML: file: lib/Weasel/FindExpanders/HTML.pm version: '0.32' Weasel::Session: file: lib/Weasel/Session.pm version: '0.32' Weasel::WidgetHandlers: file: lib/Weasel/WidgetHandlers.pm version: '0.32' Weasel::Widgets::HTML: file: lib/Weasel/Widgets/HTML.pm version: '0.32' Weasel::Widgets::HTML::Button: file: lib/Weasel/Widgets/HTML/Button.pm version: '0.32' Weasel::Widgets::HTML::Input: file: lib/Weasel/Widgets/HTML/Input.pm version: '0.32' Weasel::Widgets::HTML::Radio: file: lib/Weasel/Widgets/HTML/Radio.pm version: '0.32' Weasel::Widgets::HTML::Select: file: lib/Weasel/Widgets/HTML/Select.pm version: '0.32' Weasel::Widgets::HTML::Selectable: file: lib/Weasel/Widgets/HTML/Selectable.pm version: '0.32' recommends: Weasel::Driver::Selenium2: '0' requires: HTML::Selector::XPath: '0' List::Util: '0' Module::Runtime: '0' Moose: '0' Weasel::DriverRole: '0.04' namespace::autoclean: '0' perl: v5.12.0 resources: bugtracker: https://github.com/perl-weasel/weasel/issues homepage: https://weasel.pm/ repository: https://github.com/perl-weasel/weasel.git version: '0.32' x_contributors: - 'Brock Wilcox ' - 'Yves Lavoie ' x_generated_by_perl: v5.38.2 x_serialization_backend: 'YAML::Tiny version 1.76' x_spdx_expression: 'Artistic-1.0-Perl OR GPL-1.0-or-later' MANIFEST100644001750001750 120215013700014 13121 0ustar00erikerik000000000000Weasel-0.32# This file was automatically generated by Dist::Zilla::Plugin::Manifest v6.033. CHANGES LICENSE MANIFEST META.json META.yml Makefile.PL README README.md dist.ini lib/Weasel.pm lib/Weasel/Element.pm lib/Weasel/Element/Document.pm lib/Weasel/FindExpanders.pm lib/Weasel/FindExpanders/HTML.pm lib/Weasel/Session.pm lib/Weasel/WidgetHandlers.pm lib/Weasel/Widgets/HTML.pm lib/Weasel/Widgets/HTML/Button.pm lib/Weasel/Widgets/HTML/Input.pm lib/Weasel/Widgets/HTML/Radio.pm lib/Weasel/Widgets/HTML/Select.pm lib/Weasel/Widgets/HTML/Selectable.pm t/00-load.t t/01-logging.t t/01-test.t t/author-pod-coverage.t t/author-pod-syntax.t xt/perlcriticrc dist.ini100644001750001750 240015013700014 13435 0ustar00erikerik000000000000Weasel-0.32name = Weasel abstract = PHP's Mink inspired multi-protocol web-testing library for Perl ;' version = 0.32 author = Erik Huelsmann copyright_holder = Erik Huelsmann main_module = lib/Weasel.pm license = Perl_5 [MetaResources] homepage = https://weasel.pm/ bugtracker.web = https://github.com/perl-weasel/weasel/issues repository.url = https://github.com/perl-weasel/weasel.git repository.web = https://github.com/perl-weasel/weasel repository.type = git [@Filter] -bundle = @Basic -remove = GatherDir [Git::GatherDir] [MetaJSON] [MetaProvides::Package] [ContributorsFromGit] [Prereqs] ; Perl 5.12.0 because we want to use 'package PACKAGENAME VERSION' perl = 5.12.0 HTML::Selector::XPath = 0 List::Util = 0 Moose = 0 Module::Runtime = 0 namespace::autoclean = 0 ; We don''t depend on the driver directly, but here we set the minimum ; version we need for Weasel::Session to interact with an implementation Weasel::DriverRole = 0.04 [Prereqs / RuntimeRecommends] Weasel::Driver::Selenium2 = 0 [Prereqs / TestRequires] Test::More = 0 [Prereqs / DevelopRequires] File::Find = 0 File::Util = 0 Perl::Critic = 0 Test::Pod::Coverage = 0 [ExtraTests] [PodCoverageTests] [PodSyntaxTests] [PodVersion] [PkgVersion] use_package = 1 README.md100644001750001750 1056715013700014 13305 0ustar00erikerik000000000000Weasel-0.32 # NAME Weasel - Perl's php/Mink-inspired abstracted web-driver framework [![Build Status](https://travis-ci.org/perl-weasel/weasel.svg?branch=master)](https://travis-ci.org/perl-weasel/weasel) # VERSION 0.29 # SYNOPSIS ```perl use Weasel; use Weasel::Session; use Weasel::Driver::Selenium2; my $weasel = Weasel->new( default_session => 'default', sessions => { default => Weasel::Session->new( driver => Weasel::Driver::Selenium2->new(%opts), ), }); $weasel->session->get('http://localhost/index'); ``` # DESCRIPTION This module abstracts away the differences between the various web-driver protocols, like the Mink project does for PHP. While heavily inspired by Mink, `Weasel` aims to improve over it by being extensible, providing not just access to the underlying browser, yet to provide building blocks for further development and abstraction. [Pherkin::Extension::Weasel](https://github.com/perl-weasel/pherkin-extension-weasel) provides integration with [Test::BDD::Cucumber](https://github.com/pjlsergeant/test-bdd-cucumber-perl) (aka pherkin), for BDD testing. For the actual page interaction, this module needs a driver to be installed. Currently, that means [Weasel::Driver::Selenium2](https://github.com/perl-weasel/weasel-driver-selenium2). Other driver implementations, such as [Sahi](http://sahipro.com/) can be independently developed and uploaded to CPAN, or contributed. (We welcome and encourage both!) ## DIFFERENCES WITH OTHER FRAMEWORKS ### Mnemonics for element lookup patterns The central registry of xpath expressions to find common page elements helps to keep page access code clean. E.g. compare: ```perl use Weasel::FindExpanders::HTML; $session->page->find('*contains', text => 'Some text'); ``` With ```perl $session->page->find(".//*[contains(.,'Some text')] [not(.//*[contains(.,'Some text')])]"); ``` Multiple patterns can be registered for a single mnemonic. These which be concatenated into a single xpath expression. This concatenated expression allows to efficiently find matching elemnets with a single driver query. Besides good performance, this has the benefit that the following ```perl $session->page->find('*button', text => 'Click!'); ``` can be easily extended to match [Dojo toolkit's](http://dojotoolkit.org/documentation/) buttons as well as regular buttens. The problem with Dojo's buttons is that their DOM tree doesn't actually contain (visible) BUTTON or INPUT tags. To load support for Dojo widgets, simply: ```perl use Weasel::Widgets::Dojo; ``` ### Widgets encapsulate specific behaviours All elements in `Weasel` are of the base type `Weasel::Element`, which encapsulates the regular element interactions (click, find children, etc). While most elements will be represented by `Weasel::Element`, it's possible to implement other wrappers. These offer a logical extension point to implement tag-specific utility functions. E.g. `Weasel::Widgets::HTML::Select`, which adds the utility function `select_option`. These widgets also offer a good way to override default behaviours. One such case is the Dojo implementation of a `select` element. This element replaces the select tag entirely and in contrast with the original, doesn't keep the options as child elements of the `select`-replacing tag. By using the Dojo widget library ```perl use Weasel::Widget::Dojo; ``` the lack of the parent/child relation between the the select and its options is transparently handled by overriding the widget's `find` and `find_all` methods. # INSTALLATION ```sh # Install Weasel $ cpanm Weasel # Install Weasel's web driver $ cpanm Weasel::Driver::Selenium2 ``` If you want to use Weasel's support for Dojo-widget interaction, also: ```sh $ cpanm Weasel::Widgets::Dojo ``` If you want to use Weasel with its Pherkin (BDD) integration, also: ```sh $ cpanm Pherkin::Extension::Weasel ``` # SUPPORT ## BUGS Bugs can be filed in the GitHub issue tracker for the Weasel project: https://github.com/perl-weasel/weasel/issues ## DISCUSSION Community support is available through [perl-weasel@googlegroups.com](mailto:perl-weasel@googlegroups.com). Chat support is available in the [#perl-weasel:matrix.org](https://vector.im/beta/#/room/#perl-weasel:matrix.org) channel # COPYRIGHT ``` Copyright (c) 2016-2020 Erik Huelsmann ``` # LICENSE Same as Perl META.json100644001750001750 707315013700014 13425 0ustar00erikerik000000000000Weasel-0.32{ "abstract" : "PHP's Mink inspired multi-protocol web-testing library for Perl", "author" : [ "Erik Huelsmann " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.033, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Weasel", "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "develop" : { "requires" : { "File::Find" : "0", "File::Util" : "0", "Perl::Critic" : "0", "Pod::Coverage::TrustPod" : "0", "Test::Pod" : "1.41", "Test::Pod::Coverage" : "1.08" } }, "runtime" : { "recommends" : { "Weasel::Driver::Selenium2" : "0" }, "requires" : { "HTML::Selector::XPath" : "0", "List::Util" : "0", "Module::Runtime" : "0", "Moose" : "0", "Weasel::DriverRole" : "0.04", "namespace::autoclean" : "0", "perl" : "v5.12.0" } }, "test" : { "requires" : { "Test::More" : "0" } } }, "provides" : { "Weasel" : { "file" : "lib/Weasel.pm", "version" : "0.32" }, "Weasel::Element" : { "file" : "lib/Weasel/Element.pm", "version" : "0.32" }, "Weasel::Element::Document" : { "file" : "lib/Weasel/Element/Document.pm", "version" : "0.32" }, "Weasel::FindExpanders" : { "file" : "lib/Weasel/FindExpanders.pm", "version" : "0.32" }, "Weasel::FindExpanders::HTML" : { "file" : "lib/Weasel/FindExpanders/HTML.pm", "version" : "0.32" }, "Weasel::Session" : { "file" : "lib/Weasel/Session.pm", "version" : "0.32" }, "Weasel::WidgetHandlers" : { "file" : "lib/Weasel/WidgetHandlers.pm", "version" : "0.32" }, "Weasel::Widgets::HTML" : { "file" : "lib/Weasel/Widgets/HTML.pm", "version" : "0.32" }, "Weasel::Widgets::HTML::Button" : { "file" : "lib/Weasel/Widgets/HTML/Button.pm", "version" : "0.32" }, "Weasel::Widgets::HTML::Input" : { "file" : "lib/Weasel/Widgets/HTML/Input.pm", "version" : "0.32" }, "Weasel::Widgets::HTML::Radio" : { "file" : "lib/Weasel/Widgets/HTML/Radio.pm", "version" : "0.32" }, "Weasel::Widgets::HTML::Select" : { "file" : "lib/Weasel/Widgets/HTML/Select.pm", "version" : "0.32" }, "Weasel::Widgets::HTML::Selectable" : { "file" : "lib/Weasel/Widgets/HTML/Selectable.pm", "version" : "0.32" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/perl-weasel/weasel/issues" }, "homepage" : "https://weasel.pm/", "repository" : { "type" : "git", "url" : "https://github.com/perl-weasel/weasel.git", "web" : "https://github.com/perl-weasel/weasel" } }, "version" : "0.32", "x_contributors" : [ "Brock Wilcox ", "Yves Lavoie " ], "x_generated_by_perl" : "v5.38.2", "x_serialization_backend" : "Cpanel::JSON::XS version 4.39", "x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later" } Makefile.PL100644001750001750 250215013700014 13746 0ustar00erikerik000000000000Weasel-0.32# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v6.033. use strict; use warnings; use 5.012000; use ExtUtils::MakeMaker; my %WriteMakefileArgs = ( "ABSTRACT" => "PHP's Mink inspired multi-protocol web-testing library for Perl", "AUTHOR" => "Erik Huelsmann ", "CONFIGURE_REQUIRES" => { "ExtUtils::MakeMaker" => 0 }, "DISTNAME" => "Weasel", "LICENSE" => "perl", "MIN_PERL_VERSION" => "5.012000", "NAME" => "Weasel", "PREREQ_PM" => { "HTML::Selector::XPath" => 0, "List::Util" => 0, "Module::Runtime" => 0, "Moose" => 0, "Weasel::DriverRole" => "0.04", "namespace::autoclean" => 0 }, "TEST_REQUIRES" => { "Test::More" => 0 }, "VERSION" => "0.32", "test" => { "TESTS" => "t/*.t" } ); my %FallbackPrereqs = ( "HTML::Selector::XPath" => 0, "List::Util" => 0, "Module::Runtime" => 0, "Moose" => 0, "Test::More" => 0, "Weasel::DriverRole" => "0.04", "namespace::autoclean" => 0 ); unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) { delete $WriteMakefileArgs{TEST_REQUIRES}; delete $WriteMakefileArgs{BUILD_REQUIRES}; $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; } delete $WriteMakefileArgs{CONFIGURE_REQUIRES} unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; WriteMakefile(%WriteMakefileArgs); t000755001750001750 015013700014 12100 5ustar00erikerik000000000000Weasel-0.3200-load.t100644001750001750 26215013700014 13541 0ustar00erikerik000000000000Weasel-0.32/t#!perl use strict; use warnings; use Test::More; use_ok($_) for (qw(Weasel Weasel::Session Weasel::Element Weasel::Element::Document Weasel::DriverRole)); done_testing; 01-test.t100644001750001750 17215013700014 13602 0ustar00erikerik000000000000Weasel-0.32/t use Test::More; plan skip_all => 'Missing WHEREAREMYTESTS' unless $ENV{WHEREAREMYTESTS}; ok 'got here'; done_testing; lib000755001750001750 015013700014 12403 5ustar00erikerik000000000000Weasel-0.32Weasel.pm100644001750001750 1761615013700014 14354 0ustar00erikerik000000000000Weasel-0.32/lib =head1 NAME Weasel - Perl's php/Mink-inspired abstracted web-driver framework =head1 VERSION version 0.32 =head1 SYNOPSIS use Weasel; use Weasel::Session; use Weasel::Driver::Selenium2; my $weasel = Weasel->new( default_session => 'default', sessions => { default => Weasel::Session->new( driver => Weasel::Driver::Selenium2->new(%opts), ), }); $weasel->session->get('http://localhost/index'); =head1 DESCRIPTION This module abstracts away the differences between the various web-driver protocols, like the Mink project does for PHP. While heavily inspired by Mink, C aims to improve over it by being extensible, providing not just access to the underlying browser, yet to provide building blocks for further development and abstraction. L provides integration with L (aka pherkin), for BDD testing. For the actual page interaction, this module needs a driver to be installed. Currently, that means L. Other driver implementations, such as L can be independently developed and uploaded to CPAN, or contributed. (We welcome and encourage both!) =head2 Differences with other frameworks =over =item Mnemonics for element lookup patterns The central registry of xpath expressions to find common page elements helps to keep page access code clean. E.g. compare: use Weasel::FindExpanders::HTML; $session->page->find('*contains', text => 'Some text'); With $session->page->find(".//*[contains(.,'Some text')] [not(.//*[contains(.,'Some text')])]"); Multiple patterns can be registered for a single mnemonic, which will be concatenated to a single xpath expression to find the matching tags in a single driver query. Besides good performance, this has the benefit that the following $session->page->find('*button', text => 'Click!'); can be easily extended to match L buttons, which on the HTML level don't contain visible button or input tags, simply by using the widget support set: use Weasel::Widgets::Dojo; =item Widgets encapsulate specific behaviours All elements in C are of the base type C, which encapsulates the regular element interactions (click, find children, etc). While most elements will be represented by C, it's possible to implement other wrappers. These offer a logical extension point to implement tag-specific utility functions. E.g. C, which adds the utility function C. These widgets also offer a good way to override default behaviours. One such case is the Dojo implementation of a 'select' element. This element replaces the select tag entirely and in contrast with the original, doesn't keep the options as child elements of the 'select'-replacing tag. By using the Dojo widget library use Weasel::Widget::Dojo; the lack of the parent/child relation between the the select and its options is transparently handled by overriding the widget's C and C methods. =back =head2 PageObject architecture pattern support The functionality to encapsulate behaviours in widgets, enables intuitive integration of the L for design of test code. This pattern allows reduction of test code due and separation of concerns between the functional test and the page interaction. =cut =head1 DEPENDENCIES =cut package Weasel 0.32; use strict; use warnings; use Moose; use namespace::autoclean; # From https://w3c.github.io/webdriver/webdriver-spec.html#keyboard-actions my %key_codes = ( NULL => "\N{U+E000}", CANCEL => "\N{U+E001}", HELP => "\N{U+E002}", BACK_SPACE => "\N{U+E003}", TAB => "\N{U+E004}", CLEAR => "\N{U+E005}", RETURN => "\N{U+E006}", ENTER => "\N{U+E007}", SHIFT => "\N{U+E008}", CONTROL => "\N{U+E009}", ALT => "\N{U+E00A}", PAUSE => "\N{U+E00B}", ESCAPE => "\N{U+E00C}", SPACE => "\N{U+E00D}", PAGE_UP => "\N{U+E00E}", PAGE_DOWN => "\N{U+E00F}", 'END' => "\N{U+E010}", HOME => "\N{U+E011}", ARROW_LEFT => "\N{U+E012}", ARROW_UP => "\N{U+E013}", ARROW_RIGHT => "\N{U+E014}", ARROW_DOWN => "\N{U+E015}", INSERT => "\N{U+E016}", DELETE => "\N{U+E017}", SEMICOLON => "\N{U+E018}", EQUALS => "\N{U+E019}", NUMPAD0 => "\N{U+E01A}", NUMPAD1 => "\N{U+E01B}", NUMPAD2 => "\N{U+E01C}", NUMPAD3 => "\N{U+E01D}", NUMPAD4 => "\N{U+E01E}", NUMPAD5 => "\N{U+E01F}", NUMPAD6 => "\N{U+E020}", NUMPAD7 => "\N{U+E021}", NUMPAD8 => "\N{U+E022}", NUMPAD9 => "\N{U+E023}", MULTIPLY => "\N{U+E024}", ADD => "\N{U+E025}", SEPARATOR => "\N{U+E026}", SUBTRACT => "\N{U+E027}", DECIMAL => "\N{U+E028}", DIVIDE => "\N{U+E029}", F1 => "\N{U+E031}", F2 => "\N{U+E032}", F3 => "\N{U+E033}", F4 => "\N{U+E034}", F5 => "\N{U+E035}", F6 => "\N{U+E036}", F7 => "\N{U+E037}", F8 => "\N{U+E038}", F9 => "\N{U+E039}", F10 => "\N{U+E03A}", F11 => "\N{U+E03B}", F12 => "\N{U+E03C}", META => "\N{U+E03D}", COMMAND => "\N{U+E03D}", ZENKAKU_HANKAKU => "\N{U+E040}", ); =over =item KEYS Returns a reference to a hash with names of the keys in the hash keys and single-character strings containing the key codes as the values. =cut sub KEYS { return \%key_codes; } =back =head1 ATTRIBUTES =over =item default_session The name of the default session to return from C, in case no name argument is provided. =cut has 'default_session' => (is => 'rw', isa => 'Str', default => 'default', ); =item sessions Holds the sessions registered with the C instance. =cut has 'sessions' => (is => 'ro', isa => 'HashRef[Weasel::Session]', default => sub { {} }, ); =back =head1 SUBROUTINES/METHODS =over =item session([$name [, $value]]) Returns the session identified by C<$name>. If C<$value> is specified, it's associated with the given C<$name>. =cut sub session { my ($self, $name, $value) = @_; $name //= $self->default_session; $self->sessions->{$name} = $value if defined $value; return $self->sessions->{$name}; } =back =head1 AUTHOR Erik Huelsmann =head1 CONTRIBUTORS Erik Huelsmann Yves Lavoie =head1 MAINTAINERS Erik Huelsmann =head1 BUGS AND LIMITATIONS Bugs can be filed in the GitHub issue tracker for the Weasel project: L =head1 SOURCE The source code repository for Weasel is at https://github.com/perl-weasel/weasel =head1 SUPPORT Community support is available through L. =head1 BUGS Bugs can be filed in the GitHub issue tracker for the Weasel project: L =head1 SOURCE The source code repository for Weasel is at L =head1 SUPPORT Community support is available through L. =head1 LICENSE AND COPYRIGHT (C) 2016-2023 Erik Huelsmann Licensed under the same terms as Perl. =cut __PACKAGE__->meta->make_immutable; 1; 01-logging.t100644001750001750 367615013700014 14305 0ustar00erikerik000000000000Weasel-0.32/t#!perl use Data::Dumper; use Test::More; package DummyDriver; use Data::Dumper; use Moose; with 'Weasel::DriverRole'; sub implements { return $Weasel::DriverRole::VERSION; } sub get_attribute { return ''; } sub tag_name { my ($self, $tag) = @_; return $tag->{tag}; } sub find_all { my @rv = ( { tag => 'span' }, { tag => 'span' }, ); return (wantarray) ? @rv : \@rv; } sub screenshot { return; } package main; use Weasel; use Weasel::Session; my @logs; my $weasel = Weasel->new( default_session => 'default', sessions => { default => Weasel::Session->new( driver => DummyDriver->new(), log_hook => sub { my ($event, $item) = @_; $item = $item->() if ref $item eq 'CODE'; push @logs, [ $event, $item ]; }, ), }, ); my $session = $weasel->session; # Specifically test `find_all' due to the complex nature: # It can return an array ref in scalar context or an array in # list context -- yet the logger will receive an array ref (always) my @found = $session->page->find_all('span'); my $found = $session->page->find_all('span'); # `find_all' uses a different calling pattern than `screenshot' # and `is_displayed' $session->screenshot; is(scalar(@found), 2, 'Number of tags found equals two'); is(ref $found, 'ARRAY', 'Scalar context returns ARRAYREF'); is_deeply(\@logs, [['pre_find_all', 'pattern: span(span)'], ['post_find_all', 'found 2 elements for span - Weasel::Element (span) - Weasel::Element (span)'], ['pre_find_all', 'pattern: span(span)'], ['post_find_all', 'found 2 elements for span - Weasel::Element (span) - Weasel::Element (span)'], ['pre_screenshot', 'screenshot'], ['post_screenshot', 'screenshot'], ], 'Compare log output'); done_testing; xt000755001750001750 015013700014 12270 5ustar00erikerik000000000000Weasel-0.32perlcriticrc100644001750001750 337515013700014 15050 0ustar00erikerik000000000000Weasel-0.32/xt# Fail if listed policy modules are not available profile-strictness = fatal # 1 is the most strict setting, which is the # default when only == 1 severity = 1 verbose = %p in %f[%l:%c] = %m %r\n pager = less theme = exclude = 'OTRS::' [CodeLayout::ProhibitHardTabs] allow_leading_tabs = 0 [-CodeLayout::RequireTidyCode] [ControlStructures::ProhibitPostfixControls] allow = for if unless [-Documentation::PodSpelling] [-Documentation::RequirePodAtEnd] [-Documentation::RequirePodLinksIncludeText] [Documentation::RequirePodSections] lib_sections = NAME | VERSION | SYNOPSIS | DESCRIPTION | DEPENDENCIES | SUBROUTINES/METHODS | AUTHOR | BUGS AND LIMITATIONS | SOURCE | LICENSE AND COPYRIGHT [Modules::ProhibitEvilModules] modules = Carp::Always Data::Dumper Data::Printer [-Modules::RequireVersionVar] [-RegularExpressions::ProhibitEscapedMetacharacters] [-RegularExpressions::RequireBracesForMultiline] [-RegularExpressions::RequireDotMatchAnything] [-RegularExpressions::RequireLineBoundaryMatching] # The following requires Critic 1.36. disable until released #[Subroutines::ProhibitUnusedPrivateSubroutines] #allow_name = _build_\w+ [-Subroutines::ProhibitUnusedPrivateSubroutines] [Subroutines::RequireArgUnpacking] [-ValuesAndExpressions::ProhibitEmptyQuotes] [-Variables::ProhibitLocalVars] [Variables::ProhibitPunctuationVars] string_mode = thorough #-------------------------------------------------------------- # I think these are really important, so always load them [TestingAndDebugging::RequireUseStrict] severity = 1 [TestingAndDebugging::RequireUseWarnings] severity = 1 #-------------------------------------------------------------- # For all other Policies, I accept the default severity, # so no additional configuration is required for them. Weasel000755001750001750 015013700014 13623 5ustar00erikerik000000000000Weasel-0.32/libElement.pm100644001750001750 1303215013700014 15731 0ustar00erikerik000000000000Weasel-0.32/lib/Weasel =head1 NAME Weasel::Element - The base HTML/Widget element class =head1 VERSION version 0.32 =head1 SYNOPSIS my $element = $session->page->find("./input[\@name='phone']"); my $value = $element->send_keys('555-885-321'); =head1 DESCRIPTION This module provides the base class for all page elements, encapsulating the regular element interactions, such as finding child element, querying attributes and the tag name, etc. =cut =head1 DEPENDENCIES =cut package Weasel::Element 0.32; use strict; use warnings; use Moose; use namespace::autoclean; =head1 ATTRIBUTES =over =item session Required. Holds a reference to the L to which the element belongs. Used to access the session's driver to query element properties.x =cut has session => (is => 'ro', isa => 'Weasel::Session', required => 1, ); =item _id Required. Holds the I used by the session's driver to identify the element. =cut has _id => (is => 'ro', required => 1); =back =head1 SUBROUTINES/METHODS =over =item find($locator [, scheme => $scheme] [, widget_args => \@args ] [, %locator_args]) Finds the first child element matching c<$locator>. Returns C when not found. Optionally takes a scheme argument to identify non-xpath type locators. In case the C<$locator> is a mnemonic (starts with an asterisk ['*']), additional arguments may be provided for expansion of the mnemonic. See L for documentation of the standard expanders. Any arguments passed in the C<$widget_args> array reference, are passed to the widget's constructor. =cut sub find { my ($self, @args) = @_; return $self->session->find($self, @args); } =item find_all($locator [, scheme => $scheme] [, widget_args => \@args ] [, %locator_args]) Returns, depending on scalar vs array context, a list or an arrayref with matching elements. Returns an empty list or ref to an empty array when none found. Optionally takes a scheme argument to identify non-xpath type locators. In case the C<$locator> is a mnemonic (starts with an asterisk ['*']), additional arguments may be provided for expansion of the mnemonic. See L for documentation of the standard expanders. Any arguments passed in the C<$widget_args> array reference, are passed to the widget's constructor. =cut sub find_all { my ($self, @args) = @_; # expand $locator based on framework plugins (e.g. Dojo) return $self->session->find_all($self, @args); } =item get_attribute($attribute) Returns the value of the element's attribute named in C<$attribute> or C if none exists. Note: Some browsers apply default values to attributes which are not part of the original page. As such, there's no direct relation between the existence of attributes in the original page and this function returning C. Note: Those users familiar with Selenium might be looking for a method called C or C. Weasel doesn't have that short-hand. Please use C/C with an attribute name of C instead. =cut sub get_attribute { my ($self, $attribute) = @_; return $self->session->get_attribute($self, $attribute); } =item set_attribute($attribute, $value) Sets the value of the element's attribute named in C<$attribute>. Note: Those users familiar with Selenium might be looking for a method called C or C. Weasel doesn't have that short-hand. Please use C/C with an attribute name of C instead. =cut sub set_attribute { my ($self, $attribute, $value) = @_; return $self->session->set_attribute($self, $attribute, $value); } =item get_text() Returns the element's 'innerHTML'. =cut sub get_text { my ($self) = @_; return $self->session->get_text($self); } =item has_class =cut sub has_class { my ($self, $class) = @_; return grep { $_ eq $class } split /\s+/x, ($self->get_attribute('class') // ''); } =item is_displayed Returns a boolean indicating if an element is visible (e.g. can potentially be scrolled into the viewport for interaction). =cut sub is_displayed { my ($self) = @_; return $self->session->is_displayed($self); } =item click() Scrolls the element into the viewport and simulates it being clicked on. =cut sub click { my ($self) = @_; return $self->session->click($self); } =item send_keys(@keys) Focusses the element and simulates keyboard input. C<@keys> can be any number of strings containing unicode characters to be sent. E.g. $element->send_keys("hello", ' ', "world"); =cut sub send_keys { my ($self, @keys) = @_; return $self->session->send_keys($self, @keys); } =item tag_name() Returns the name of the tag of the element, e.g. 'div' or 'input'. =cut sub tag_name { my ($self) = @_; return $self->session->tag_name($self); } =back =cut =head1 AUTHOR Erik Huelsmann =head1 CONTRIBUTORS Erik Huelsmann Yves Lavoie =head1 MAINTAINERS Erik Huelsmann =head1 BUGS AND LIMITATIONS Bugs can be filed in the GitHub issue tracker for the Weasel project: https://github.com/perl-weasel/weasel/issues =head1 SOURCE The source code repository for Weasel is at https://github.com/perl-weasel/weasel =head1 SUPPORT Community support is available through L. =head1 LICENSE AND COPYRIGHT (C) 2016-2023 Erik Huelsmann Licensed under the same terms as Perl. =cut __PACKAGE__->meta->make_immutable; 1; Session.pm100644001750001750 4107115013700014 15767 0ustar00erikerik000000000000Weasel-0.32/lib/Weasel =head1 NAME Weasel::Session - Connection to an encapsulated test driver =head1 VERSION version 0.32 =head1 SYNOPSIS use Weasel; use Weasel::Session; use Weasel::Driver::Selenium2; my $weasel = Weasel->new( default_session => 'default', sessions => { default => Weasel::Session->new( driver => Weasel::Driver::Selenium2->new(%opts), ), }); $weasel->session->get('http://localhost/index'); =head1 DESCRIPTION The session represents a connection to a browser window, allowing interaction with that window. It abstracts from the protocol being used for such access; meaning that the true interactions may be achieved through Selenium, W3C Web Driver, Cypress, Playwright or any other protocol or access method as long as the driver adheres to the L protocol of the required minimum version. =cut =head1 DEPENDENCIES =cut package Weasel::Session 0.32; use strict; use warnings; use Moose; use namespace::autoclean; use HTML::Selector::XPath; use Module::Runtime qw/ use_module /;; use Weasel::FindExpanders qw/ expand_finder_pattern /; use Weasel::WidgetHandlers qw| best_match_handler_class |; our $MINIMUM_DRIVER_VERSION = '0.03'; =head1 ATTRIBUTES =over =item driver Holds a reference to the sessions's driver. =cut has 'driver' => (is => 'ro', required => 1, handles => { '_start' => 'start', 'stop' => 'stop', '_restart' => 'restart', 'started' => 'started', }, ); =item widget_groups Contains the list of widget groups to be used with the session, or uses all groups when undefined. Note: this functionality allows one to load multiple groups into the running perl instance, while using different groups in various sessions. =cut has 'widget_groups' => (is => 'rw'); =item base_url Holds the prefix that will be prepended to every URL passed to this API. The prefix can be an environment variable, e.g. ${VARIABLE}. It will be expanded and default to hppt://localhost:5000 if not defined. If it is not an environment variable, it will be used as is. =cut has 'base_url' => (is => 'rw', isa => 'Str', default => '', ); =item page Holds the root element of the target HTML page (the 'html' tag). =cut has 'page' => (is => 'ro', isa => 'Weasel::Element::Document', builder => '_build_page', lazy => 1, ); sub _build_page { my $self = shift; my $class = use_module($self->page_class); return $class->new(session => $self); } =item log_hook Upon instantiation can be set to log consumer; a function of 3 arguments: 1. the name of the event 2. the text to be logged (or a coderef to be called without arguments returning such) =cut has 'log_hook' => (is => 'ro', isa => 'Maybe[CodeRef]', ); =item page_class Upon instantiation can be set to an alternative class name for the C attribute. =cut has 'page_class' => (is => 'ro', isa => 'Str', default => 'Weasel::Element::Document', ); =item retry_timeout The number of seconds to poll for a condition to become true. Global setting for the C function. =cut has 'retry_timeout' => (is => 'rw', default => 15, isa => 'Num', ); =item poll_delay The number of seconds to wait between state polling attempts. Global setting for the C function. =cut has 'poll_delay' => (is => 'rw', default => 0.5, isa => 'Num', ); =item state Holds one of =over =item * initial =item * started =item * stopped =back Before the first page is loaded into the browser, the value of the C property is C. After the first C call, the value changes to C. =cut has 'state' => (is => 'rw', default => 'initial', isa => 'Str'); =back =head1 SUBROUTINES/METHODS =over =item clear($element) Clears any input entered into elements supporting it. Generally applies to textarea elements and input elements of type text and password. =cut sub clear { my ($self, $element) = @_; return $self->_logged(sub { $self->driver->clear($element->_id); }, 'clear', 'clearing input element'); } =item click([$element]) Simulates a single mouse click. If an element argument is provided, that element is clicked. Otherwise, the browser window is clicked at the current mouse location. =cut sub click { my ($self, $element) = @_; return $self->_logged( sub { $self->driver->click(($element) ? $element->_id : undef); }, 'click', ($element) ? 'clicking element' : 'clicking window'); } =item find($element, $locator [, scheme => $scheme] [, widget_args => \@args ] [, %locator_args]) Finds the first child of C<$element> matching C<$locator>. See L's C function for more documentation. =cut sub find { my ($self, @args) = @_; my $rv; $self->_logged( sub { $self->wait_for( sub { my @rv = @{$self->find_all(@args)}; return $rv = shift @rv; }); }, 'find', 'find ' . $args[1]); return $rv; } =item find_all($element, $locator, [, scheme => $scheme] [, widget_args => \@args ] [, %locator_args ]) Finds all child elements of C<$element> matching C<$locator>. Returns, depending on scalar or list context, an arrayref or a list with matching elements. See L's C function for more documentation. =cut sub find_all { my ($self, $element, $pattern, %args) = @_; my $expanded_pattern; # if (exists $args{scheme} and $args{scheme} eq 'css') { # delete $args{scheme}; # $expanded_pattern = # q{.} . HTML::Selector::XPath->new($pattern)->to_xpath; # } # else { $expanded_pattern = expand_finder_pattern($pattern, \%args); # } my @rv = $self->_logged( sub { return map { $self->_wrap_widget($_, $args{widget_args}) } $self->driver->find_all($element->_id, $expanded_pattern, $args{scheme}); }, 'find_all', sub { my ($rv) = @_; ##no critic(ProhibitUselessTopic) return 'found ' . scalar(@{$rv}) . " elements for $expanded_pattern " . (join ', ', %args) . "\n" . (join "\n", map { ' - ' . ref($_) . ' (' . $_->tag_name . ($_->get_attribute('id') ? '#' . $_->get_attribute('id') : '') .')' } @{$rv}); }, "pattern: $pattern($expanded_pattern)"); return wantarray ? @rv : \@rv; } =item get($url) Loads C<$url> into the active browser window of the driver connection, after prefixing with C. =cut sub get { my ($self, $url) = @_; my $base = $self->base_url =~ /\$\{(\w+)\}/x ? $ENV{$1} // 'http://localhost:5000' : $self->base_url; $url = $base . $url; $self->state('started'); ###TODO add logging warning of urls without protocol part # which might indicate empty 'base_url' where one is assumed to be set return $self->_logged( sub { return $self->driver->get($url); }, 'get', "loading URL: $url"); } =item get_attribute($element, $attribute) Returns the value of the attribute named by C<$attribute> of the element identified by C<$element>, or C if the attribute isn't defined. =cut sub get_attribute { my ($self, $element, $attribute) = @_; return $self->_logged( sub { return $self->driver->get_attribute($element->_id, $attribute); }, 'get_attribute', "element attribute '$attribute'"); } =item get_text($element) Returns the 'innerHTML' of the element identified by C<$element>. =cut sub get_text { my ($self, $element) = @_; return $self->_logged( sub { return $self->driver->get_text($element->_id); }, 'get_text', 'element text'); } =item set_attribute($element_id, $attribute_name, $value) DEPRECATED Changes the value of the attribute named by C<$attribute_name> to C<$value> for the element identified by C<$element_id>. =cut sub set_attribute { my ($self, $element, $attribute, $value) = @_; return $self->_logged( sub { return $self->driver->set_attribute($element->_id, $attribute, $value); }, 'set_attribute', qq{Setting attribute $attribute to '$value'}); } =item get_selected($element_id) DEPRECATED Please use C<$self->get_attribute('selected')> instead. =cut sub get_selected { my ($self, $element) = @_; return $self->_logged( sub { return $self->driver->get_selected($element->_id); }, 'get_selected', 'Is element selected?'); } =item set_selected($element_id, $value) DEPRECATED Please use C<$self->set_attribute('selected', $value)> instead. =cut sub set_selected { my ($self, $element, $value) = @_; return $self->_logged( sub { return $self->driver->get_selected($element->_id, $value); }, 'set_selected', qq{Setting 'selected' property: $value}); } =item is_displayed($element) Returns a boolean value indicating if the element identified by C<$element> is visible on the page, i.e. that it can be scrolled into the viewport for interaction. =cut sub is_displayed { my ($self, $element) = @_; return $self->_logged( sub { return $self->driver->is_displayed($element->_id); }, 'is_displayed', 'query is_displayed'); } =item screenshot($fh) Writes a screenshot of the browser's window to the filehandle C<$fh>. Note: this version assumes pictures of type PNG will be written; later versions may provide a means to query the exact image type of screenshots being generated. =cut sub screenshot { my ($self, $fh) = @_; return $self->_logged( sub { $self->driver->screenshot($fh); }, 'screenshot', 'screenshot'); } =item start Starts a new or stopped session. Sets C back to the value C. =item restart Restarts a session by resetting it and starting. Sets C back to the value C. =item stop =item started Returns a C value when the session has been started. =cut sub start { my $self = shift; $self->_start; $self->state('initial'); } sub restart { my $self = shift; $self->_restart; $self->state('initial'); } =item get_page_source($fh) Writes a get_page_source of the browser's window to the filehandle C<$fh>. =cut sub get_page_source { my ($self,$fh) = @_; return $self->_logged( sub { $self->driver->get_page_source($fh); }, 'get_page_source', 'get_page_source'); } =item send_keys($element, @keys) Send the characters specified in the strings in C<@keys> to C<$element>, simulating keyboard input. =cut sub send_keys { my ($self, $element, @keys) = @_; return $self->_logged( sub { $self->driver->send_keys($element->_id, @keys); }, 'send_keys', 'sending keys: ' . (join '', @keys // ())); } =item tag_name($element) Returns the tag name of the element identified by C<$element>. =cut sub tag_name { my ($self, $element) = @_; return $self->_logged(sub { return $self->driver->tag_name($element->_id) }, 'tag_name', sub { my $tag = shift; return ($tag) ? "found tag with name '$tag'" : 'no tag name found' }, 'getting tag name'); } =item wait_for($callback, [ retry_timeout => $number,] [poll_delay => $number,] [ on_timeout => \&cb ]) Polls $callback->() until it returns true, or C expires -- whichever comes first. The arguments retry_timeout and poll_delay can be used to override the session-global settings. =cut sub _wrap_callback { my ($self, $cb) = @_; if (! $self->log_hook) { return $cb; } else { my $count = 0; return sub { if ($count) { my $log_hook = $self->log_hook; local $self->{log_hook} = undef; # suppress logging my $rv = $cb->(); if ($rv) { # $self->log_hook is still bound to 'undef' $log_hook->('post_wait_for', "success after $count retries"); } $count++; return $rv; } else { $count++; $self->log_hook->('pre_wait_for', 'checking wait_for conditions'); return $cb->(); } }; } } sub wait_for { my ($self, $callback, %args) = @_; return $self->_logged( sub { $self->driver->wait_for($self->_wrap_callback($callback), retry_timeout => $self->retry_timeout, poll_delay => $self->poll_delay, %args); }, 'wait_for', 'waiting for condition'); } before 'BUILDARGS', sub { my ($class, @args) = @_; my $args = (ref $args[0]) ? $args[0] : { @args }; confess "Driver used to construct session object uses old API version;\n" . 'some functionality may not work correctly' if ($args->{driver} && $args->{driver}->implements < $MINIMUM_DRIVER_VERSION); }; sub _appending_wrap { my ($str) = @_; return sub { my $rv = shift; if ($rv) { return "$str ($rv)"; } else { return $str; } } } =item _logged($wrapped_fn, $event, $log_item, $log_item_pre) Invokes C when it's defined, before and after calling C<$wrapped_fn> with no arguments, with the 'pre_' and 'post_' prefixes to the event name. C<$log_item> can be a fixed string or a function of one argument returning the string to be logged. The argument passed into the function is the value returned by the C<$wrapped_fn>. In case there is no C<$log_item_pre> to be called on the 'pre_' event, C<$log_item> will be used instead, with no arguments. For performance reasons, the C<$log_item> and C<$log_item_pre> - when coderefs - aren't called; instead they are passed as-is to the C<$log_hook> for lazy evaluation. =cut sub _unlogged { my ($self, $func) = @_; local $self->{log_hook} = undef; $func->(); return; } sub _logged { my ($self, $f, $e, $l, $lp) = @_; my $hook = $self->log_hook; return $f->() if ! defined $hook; $lp //= $l; my $pre = (ref $lp eq 'CODE') ? $lp : _appending_wrap($lp); my $post = (ref $l eq 'CODE') ? $l : _appending_wrap($l); $self->_unlogged( sub { $hook->("pre_$e", $pre); } ); if (wantarray) { my @rv = $f->(); $self->_unlogged( sub { $hook->("post_$e", sub { return $post->(\@rv); }); } ); return @rv; } else { my $rv = $f->(); $self->_unlogged( sub { $hook->("post_$e", sub { return $post->($rv); }); } ); return $rv; } }; =item _wrap_widget($_id) Finds all matching widget selectors to wrap the driver element in. In case of multiple matches, selects the most specific match (the one with the highest number of requirements). =cut sub _wrap_widget { my ($self, $_id, $widget_args) = @_; my $best_class = best_match_handler_class( $self->driver, $_id, $self->widget_groups) // 'Weasel::Element'; $widget_args //= []; return $best_class->new(_id => $_id, session => $self, @{$widget_args}); } =back =head1 SEE ALSO L =head1 AUTHOR Erik Huelsmann =head1 CONTRIBUTORS Erik Huelsmann Yves Lavoie =head1 MAINTAINERS Erik Huelsmann =head1 BUGS AND LIMITATIONS Bugs can be filed in the GitHub issue tracker for the Weasel project: https://github.com/perl-weasel/weasel/issues =head1 SOURCE The source code repository for Weasel is at https://github.com/perl-weasel/weasel =head1 SUPPORT Community support is available through L. =head1 LICENSE AND COPYRIGHT (C) 2016-2023 Erik Huelsmann Licensed under the same terms as Perl. =cut __PACKAGE__->meta->make_immutable; 1; author-pod-syntax.t100644001750001750 45415013700014 16016 0ustar00erikerik000000000000Weasel-0.32/t#!perl BEGIN { unless ($ENV{AUTHOR_TESTING}) { print qq{1..0 # SKIP these tests are for testing by the author\n}; exit } } # This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests. use strict; use warnings; use Test::More; use Test::Pod 1.41; all_pod_files_ok(); author-pod-coverage.t100644001750001750 56715013700014 16270 0ustar00erikerik000000000000Weasel-0.32/t#!perl BEGIN { unless ($ENV{AUTHOR_TESTING}) { print qq{1..0 # SKIP these tests are for testing by the author\n}; exit } } # This file was automatically generated by Dist::Zilla::Plugin::PodCoverageTests. use strict; use warnings; use Test::Pod::Coverage 1.08; use Pod::Coverage::TrustPod; all_pod_coverage_ok({ coverage_class => 'Pod::Coverage::TrustPod' }); Widgets000755001750001750 015013700014 15231 5ustar00erikerik000000000000Weasel-0.32/lib/WeaselHTML.pm100644001750001750 250315013700014 16473 0ustar00erikerik000000000000Weasel-0.32/lib/Weasel/Widgets =head1 NAME Weasel::Widgets::HTML - Helper module for bulk-registration of HTML widgets =head1 VERSION version 0.32 =head1 SYNOPSIS use Weasel::Widgets::HTML; my $button = $session->page->find('//button'); # $button is now a Weasel::Widgets::HTML::Button instance =head1 DESCRIPTION =cut =head1 DEPENDENCIES =cut package Weasel::Widgets::HTML 0.32; use strict; use warnings; use Weasel::Widgets::HTML::Button; # button, reset, image, submit, BUTTON use Weasel::Widgets::HTML::Selectable; # checkbox, radio, OPTION use Weasel::Widgets::HTML::Input; # text, password, use Weasel::Widgets::HTML::Select; # No widgets for file inputs and # more importantly TEXTAREA, FORM and SELECT =head1 SUBROUTINES/METHODS =cut =head1 AUTHOR Erik Huelsmann =head1 CONTRIBUTORS Erik Huelsmann Yves Lavoie =head1 MAINTAINERS Erik Huelsmann =head1 BUGS AND LIMITATIONS Bugs can be filed in the GitHub issue tracker for the Weasel project: https://github.com/perl-weasel/weasel/issues =head1 SOURCE The source code repository for Weasel is at https://github.com/perl-weasel/weasel =head1 SUPPORT Community support is available through L. =head1 LICENSE AND COPYRIGHT (C) 2016-2023 Erik Huelsmann Licensed under the same terms as Perl. =cut 1; FindExpanders.pm100644001750001750 711315013700014 17055 0ustar00erikerik000000000000Weasel-0.32/lib/Weasel =head1 NAME Weasel::FindExpanders - Mapping find patterns to xpath locators =head1 VERSION version 0.32 =head1 SYNOPSIS use Weasel::FindExpanders qw( register_find_expander ); register_find_expander( 'button', 'HTML', sub { my %args = @_; $args{text} =~ s/'/''/g; # quote the quotes (XPath 2.0) return ".//button[text()='$args{text}']"; }); $session->find($session->page, "*button|{text=>\"whatever\"}"); =cut =head1 DESCRIPTION The concept of I is used to define XPath templates for use with the C and C methods. Multiple patterns may be registered with the same pattern name ("mnemonic"); eg, there could be a two patterns associated with the C