Merge branch 'develop'
- Upgrade selenium tools with video recording - Improve script like search_class_model and transform python code_writer - Minor ERPLibre fix and upgrade dependencies
This commit is contained in:
commit
47fbc6fa48
18 changed files with 1660 additions and 677 deletions
4
TODO.md
4
TODO.md
|
|
@ -1,4 +1,8 @@
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
|
## Deb installation
|
||||||
|
devops_erplibre need sshpass
|
||||||
|
|
||||||
## Simplify push tag
|
## Simplify push tag
|
||||||
For RELEASE.md, replace next value by a script to select all remote by manifest file.
|
For RELEASE.md, replace next value by a script to select all remote by manifest file.
|
||||||
Actually, need to push manually all different remote.
|
Actually, need to push manually all different remote.
|
||||||
|
|
|
||||||
79
doc/schema/system_autopoieses_erplibre_industrie40.drawio
Normal file
79
doc/schema/system_autopoieses_erplibre_industrie40.drawio
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
<mxfile host="Electron" modified="2024-02-17T23:44:40.120Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/23.1.5 Chrome/120.0.6099.109 Electron/28.1.0 Safari/537.36" etag="k9-HJHHesxZkKjecO6lZ" version="23.1.5" type="device">
|
||||||
|
<diagram name="Page-1" id="71akO1XMp-x3XkFRrFtN">
|
||||||
|
<mxGraphModel dx="2214" dy="843" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0" />
|
||||||
|
<mxCell id="1" parent="0" />
|
||||||
|
<mxCell id="HL9Dqaa0VAam4Z8SZ0WZ-2" value="ERPLibre" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="60" y="260" width="820" height="880" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="HL9Dqaa0VAam4Z8SZ0WZ-13" value="" style="rounded=0;whiteSpace=wrap;html=1;" parent="HL9Dqaa0VAam4Z8SZ0WZ-2" vertex="1">
|
||||||
|
<mxGeometry x="30" y="140" width="350" height="560" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="HL9Dqaa0VAam4Z8SZ0WZ-4" value="" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" parent="HL9Dqaa0VAam4Z8SZ0WZ-2" vertex="1">
|
||||||
|
<mxGeometry x="400" y="79" width="370" height="380" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="HL9Dqaa0VAam4Z8SZ0WZ-7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="HL9Dqaa0VAam4Z8SZ0WZ-2" source="HL9Dqaa0VAam4Z8SZ0WZ-5" target="HL9Dqaa0VAam4Z8SZ0WZ-6" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="HL9Dqaa0VAam4Z8SZ0WZ-8" value="Technopoïeses" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="HL9Dqaa0VAam4Z8SZ0WZ-7" vertex="1" connectable="0">
|
||||||
|
<mxGeometry x="0.3947" y="2" relative="1" as="geometry">
|
||||||
|
<mxPoint y="-22" as="offset" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="HL9Dqaa0VAam4Z8SZ0WZ-5" value="Instance RobotLibre" style="swimlane;whiteSpace=wrap;html=1;" parent="HL9Dqaa0VAam4Z8SZ0WZ-2" vertex="1">
|
||||||
|
<mxGeometry x="500" y="188" width="200" height="200" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="HL9Dqaa0VAam4Z8SZ0WZ-6" value="Composante sur domaine expertise" style="swimlane;whiteSpace=wrap;html=1;" parent="HL9Dqaa0VAam4Z8SZ0WZ-2" vertex="1">
|
||||||
|
<mxGeometry x="130" y="223" width="235" height="130" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="HL9Dqaa0VAam4Z8SZ0WZ-11" value="Métadata du contexte Réseau" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" parent="HL9Dqaa0VAam4Z8SZ0WZ-6" vertex="1">
|
||||||
|
<mxGeometry x="27.5" y="37" width="180" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="HL9Dqaa0VAam4Z8SZ0WZ-9" value="" style="curved=1;endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="HL9Dqaa0VAam4Z8SZ0WZ-2" source="HL9Dqaa0VAam4Z8SZ0WZ-6" target="HL9Dqaa0VAam4Z8SZ0WZ-6" edge="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="230" y="450" as="sourcePoint" />
|
||||||
|
<mxPoint x="280" y="400" as="targetPoint" />
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="210" y="450" />
|
||||||
|
<mxPoint x="30" y="340" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="HL9Dqaa0VAam4Z8SZ0WZ-10" value="Regen" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="HL9Dqaa0VAam4Z8SZ0WZ-2" vertex="1">
|
||||||
|
<mxGeometry x="110" y="359" width="60" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="HL9Dqaa0VAam4Z8SZ0WZ-12" value="Réseau par DevOps" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="HL9Dqaa0VAam4Z8SZ0WZ-2" vertex="1">
|
||||||
|
<mxGeometry x="530" y="140" width="90" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="HL9Dqaa0VAam4Z8SZ0WZ-14" value="Code Generator" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="HL9Dqaa0VAam4Z8SZ0WZ-2" vertex="1">
|
||||||
|
<mxGeometry x="30" y="145" width="140" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="HL9Dqaa0VAam4Z8SZ0WZ-15" value="Build Technologie" style="swimlane;whiteSpace=wrap;html=1;" parent="HL9Dqaa0VAam4Z8SZ0WZ-2" vertex="1">
|
||||||
|
<mxGeometry x="40" y="450" width="130" height="170" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="HL9Dqaa0VAam4Z8SZ0WZ-16" value="<div align="left">Python</div><div align="left">Javascript</div><div align="left">XML</div><div align="left">CSS</div><div align="left">Bash</div><div align="left">Makefile<br></div>" style="text;html=1;align=left;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" parent="HL9Dqaa0VAam4Z8SZ0WZ-15" vertex="1">
|
||||||
|
<mxGeometry x="10" y="45" width="80" height="100" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="HL9Dqaa0VAam4Z8SZ0WZ-24" value="Technopoïeses Procedure Action" style="swimlane;whiteSpace=wrap;html=1;" parent="HL9Dqaa0VAam4Z8SZ0WZ-2" vertex="1">
|
||||||
|
<mxGeometry x="320" y="470" width="380" height="170" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="HL9Dqaa0VAam4Z8SZ0WZ-25" value="<div>Définitions des tâches -&gt; techniques sur création de technologies</div><div>Documenter via un support visuel : affichage sur site web</div><div>Reconnu, officiellement accepté par la communauté<br></div>" style="text;html=1;align=left;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" parent="HL9Dqaa0VAam4Z8SZ0WZ-24" vertex="1">
|
||||||
|
<mxGeometry x="10" y="65" width="370" height="60" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="yzaUlIPQqn0vAzBn3_-S-1" value="Version<br>Serveur et/ou client" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="HL9Dqaa0VAam4Z8SZ0WZ-2">
|
||||||
|
<mxGeometry x="175" y="480" width="115" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="HL9Dqaa0VAam4Z8SZ0WZ-3" value="Système d'autopoïèse ERPLibre Industrie 4.0" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#008a00;strokeColor=#005700;fontSize=21;fontColor=#ffffff;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="320" y="190" width="430" height="60" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="HL9Dqaa0VAam4Z8SZ0WZ-21" value="Developpeur externe" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="-160" y="580" width="30" height="60" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="HL9Dqaa0VAam4Z8SZ0WZ-23" value="Utilisateur de ERP" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="-110" y="680" width="30" height="60" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
|
|
@ -19,7 +19,9 @@ services:
|
||||||
# not behind a proxy
|
# not behind a proxy
|
||||||
#command: odoo --workers 0
|
#command: odoo --workers 0
|
||||||
# behind a proxy
|
# behind a proxy
|
||||||
#command: odoo --workers 2
|
#command: odoo --workers 2 --proxy-mode
|
||||||
|
# For production ready
|
||||||
|
#command: odoo --workers 2 --proxy-mode --no-database-list
|
||||||
command: odoo
|
command: odoo
|
||||||
volumes:
|
volumes:
|
||||||
# See the volume section at the end of the file
|
# See the volume section at the end of the file
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ RUN apt-get update \
|
||||||
libbz2-dev \
|
libbz2-dev \
|
||||||
zlib1g-dev \
|
zlib1g-dev \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
|
libcairo2-dev \
|
||||||
openssl \
|
openssl \
|
||||||
libgdbm-dev \
|
libgdbm-dev \
|
||||||
libgdbm-compat-dev \
|
libgdbm-compat-dev \
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@
|
||||||
<project name="server-tools.git" path="addons/OCA_server-tools" remote="OCA" revision="12.0_dev" groups="addons,base"/>
|
<project name="server-tools.git" path="addons/OCA_server-tools" remote="OCA" revision="12.0_dev" groups="addons,base"/>
|
||||||
<project name="server-ux.git" path="addons/OCA_server-ux" remote="OCA" groups="addons,base"/>
|
<project name="server-ux.git" path="addons/OCA_server-ux" remote="OCA" groups="addons,base"/>
|
||||||
<project name="social.git" path="addons/OCA_social" remote="OCA" groups="addons,base"/>
|
<project name="social.git" path="addons/OCA_social" remote="OCA" groups="addons,base"/>
|
||||||
<project name="stock-logistics-warehouse.git" path="addons/OCA_stock-logistics-warehouse" remote="OCA" groups="addons"/>
|
<project name="stock-logistics-warehouse.git" path="addons/OCA_stock-logistics-warehouse" remote="OCA" revision="12.0_dev" groups="addons"/>
|
||||||
<project name="storage.git" path="addons/OCA_storage" remote="OCA" groups="addons"/>
|
<project name="storage.git" path="addons/OCA_storage" remote="OCA" groups="addons"/>
|
||||||
<project name="timesheet.git" path="addons/OCA_timesheet" remote="OCA" revision="12.0_support_project_agile_timesheet" groups="addons"/>
|
<project name="timesheet.git" path="addons/OCA_timesheet" remote="OCA" revision="12.0_support_project_agile_timesheet" groups="addons"/>
|
||||||
<project name="vertical-association.git" path="addons/OCA_vertical-association" remote="OCA" groups="addons"/>
|
<project name="vertical-association.git" path="addons/OCA_vertical-association" remote="OCA" groups="addons"/>
|
||||||
|
|
|
||||||
944
poetry.lock
generated
944
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -26,36 +26,36 @@ altair = "5.0.1"
|
||||||
argcomplete = "3.1.1"
|
argcomplete = "3.1.1"
|
||||||
asterisk = "^0.0.8"
|
asterisk = "^0.0.8"
|
||||||
astor = "^0.8.1"
|
astor = "^0.8.1"
|
||||||
azure-identity = "^1.15.0"
|
azure-identity = "1.15.0"
|
||||||
azure-storage-blob = "^12.19.0"
|
azure-storage-blob = "12.19.0"
|
||||||
babel = "2.9.1"
|
babel = "2.9.1"
|
||||||
beautifulsoup4 = "^4.12.2"
|
beautifulsoup4 = "^4.12.3"
|
||||||
bokeh = "1.1.0"
|
bokeh = "1.1.0"
|
||||||
boto3 = ">=1.20.41"
|
boto3 = "1.33.13"
|
||||||
cachetools = ">=2.0.1"
|
cachetools = ">=2.0.1"
|
||||||
cerberus = "^1.3.5"
|
cerberus = "^1.3.5"
|
||||||
chardet = "5.0.0"
|
chardet = "5.0.0"
|
||||||
click = "^8.1.7"
|
click = "^8.1.7"
|
||||||
cloudflare = "^2.14.3"
|
cloudflare = "^3.0.1"
|
||||||
code-writer = "^1.1.1"
|
code-writer = "1.1.1"
|
||||||
colorama = "^0.4.6"
|
colorama = "^0.4.6"
|
||||||
configparser = "5.0.2"
|
configparser = "5.0.2"
|
||||||
coverage = "7.2.7"
|
coverage = "7.2.7"
|
||||||
cryptography = "39.0.1"
|
cryptography = "39.0.1"
|
||||||
css-html-prettify = "^2.5.5"
|
css-html-prettify = "^2.5.5"
|
||||||
cython = "^3.0.6"
|
cython = "^3.0.10"
|
||||||
ddt = "1.2.0"
|
ddt = "1.2.0"
|
||||||
decorator = "4.0.10"
|
decorator = "4.0.10"
|
||||||
dnspython = "2.3.0"
|
dnspython = "2.3.0"
|
||||||
docutils = "0.17.1"
|
docutils = "0.17.1"
|
||||||
ebaysdk = "2.1.5"
|
ebaysdk = "2.1.5"
|
||||||
email-validator = "2.1.0"
|
email-validator = "2.0.0"
|
||||||
emoji = "^2.9.0"
|
emoji = "^2.12.1"
|
||||||
extract-msg = "0.36.1"
|
extract-msg = "0.36.1"
|
||||||
factur-x = "^3.1"
|
factur-x = "^3.1"
|
||||||
feedparser = "6.0.10"
|
feedparser = "6.0.10"
|
||||||
flake8 = "<6.0.0"
|
flake8 = "<6.0.0"
|
||||||
formio-data = "^1.2.4"
|
formio-data = "^2.0.3"
|
||||||
freezegun = "1.2.2"
|
freezegun = "1.2.2"
|
||||||
future = "0.18.3"
|
future = "0.18.3"
|
||||||
geojson = "<3.0.0"
|
geojson = "<3.0.0"
|
||||||
|
|
@ -63,7 +63,9 @@ gevent = "1.5.0"
|
||||||
gitpython = "3.1.30"
|
gitpython = "3.1.30"
|
||||||
giturlparse = "0.10.0"
|
giturlparse = "0.10.0"
|
||||||
greenlet = "0.4.14"
|
greenlet = "0.4.14"
|
||||||
|
gst = "^0.1.0"
|
||||||
html2text = "2016.9.19"
|
html2text = "2016.9.19"
|
||||||
|
humanize = "4.6.0"
|
||||||
invoice2data = "0.3.5"
|
invoice2data = "0.3.5"
|
||||||
iscompatible = "^0.1.1"
|
iscompatible = "^0.1.1"
|
||||||
isort = "<5.12.0"
|
isort = "<5.12.0"
|
||||||
|
|
@ -80,7 +82,7 @@ mako = "1.2.2"
|
||||||
markupsafe = "0.23"
|
markupsafe = "0.23"
|
||||||
mmg = "1.0.3"
|
mmg = "1.0.3"
|
||||||
mock = "2.0.0"
|
mock = "2.0.0"
|
||||||
mpld3 = "^0.5.9"
|
mpld3 = "^0.5.10"
|
||||||
mysqlclient = "2.1.1"
|
mysqlclient = "2.1.1"
|
||||||
num2words = "0.5.6"
|
num2words = "0.5.6"
|
||||||
numpy = "1.21.1"
|
numpy = "1.21.1"
|
||||||
|
|
@ -88,18 +90,18 @@ oauthlib = "2.1.0"
|
||||||
oca-decorators = "^0.0.1"
|
oca-decorators = "^0.0.1"
|
||||||
odoorpc = "^0.10.1"
|
odoorpc = "^0.10.1"
|
||||||
ofxparse = "0.16"
|
ofxparse = "0.16"
|
||||||
openpyxl = "^3.1.2"
|
openpyxl = "3.1.3"
|
||||||
openupgradelib = "^3.5.0"
|
openupgradelib = "^3.6.1"
|
||||||
orjson = "3.6.0"
|
orjson = "3.6.0"
|
||||||
ovh = "^1.1.0"
|
ovh = "^1.1.2"
|
||||||
paho-mqtt = "^1.6.1"
|
paho-mqtt = "^2.1.0"
|
||||||
pandas = "1.3.5"
|
pandas = "1.3.5"
|
||||||
paramiko = "^3.3.1"
|
paramiko = "^3.4.0"
|
||||||
passlib = "1.6.5"
|
passlib = "1.6.5"
|
||||||
pdf2image = "^1.16.3"
|
pdf2image = "^1.17.0"
|
||||||
pdfminer = "^20191125"
|
pdfminer = "^20191125"
|
||||||
pexpect = "^4.9.0"
|
pexpect = "^4.9.0"
|
||||||
phonenumbers = "^8.13.26"
|
phonenumbers = "^8.13.40"
|
||||||
pillow = "9.3.0"
|
pillow = "9.3.0"
|
||||||
plotly = "4.1.0"
|
plotly = "4.1.0"
|
||||||
portalocker = "1.7.1"
|
portalocker = "1.7.1"
|
||||||
|
|
@ -114,12 +116,14 @@ pycountry = "22.3.5"
|
||||||
pydot = "1.2.3"
|
pydot = "1.2.3"
|
||||||
pygments = "2.7.4"
|
pygments = "2.7.4"
|
||||||
pygments-csv-lexer = ">=0.1,<1.0"
|
pygments-csv-lexer = ">=0.1,<1.0"
|
||||||
|
pygobject = "3.44.1"
|
||||||
pygount = ">1.2.1"
|
pygount = ">1.2.1"
|
||||||
pyjsparser = "^2.7.1"
|
pyjsparser = "^2.7.1"
|
||||||
pyjwt = "2.5.0"
|
pyjwt = "2.5.0"
|
||||||
pyldap = "2.4.28"
|
pyldap = "2.4.28"
|
||||||
pymssql = "^2.2.11"
|
pylint-odoo = "8.0.19"
|
||||||
pymysql = "^1.1.0"
|
pymssql = "^2.3.0"
|
||||||
|
pymysql = "^1.1.1"
|
||||||
pyotp = "^2.9.0"
|
pyotp = "^2.9.0"
|
||||||
pyparsing = "2.1.10"
|
pyparsing = "2.1.10"
|
||||||
pypdf2 = "1.27.9"
|
pypdf2 = "1.27.9"
|
||||||
|
|
@ -134,8 +138,10 @@ python-jose = "^3.3.0"
|
||||||
python-json-logger = "0.1.5"
|
python-json-logger = "0.1.5"
|
||||||
python-keystoneclient = "3.22.0"
|
python-keystoneclient = "3.22.0"
|
||||||
python-ldap = "^3.4.4"
|
python-ldap = "^3.4.4"
|
||||||
|
python-pptx = "^0.6.23"
|
||||||
|
python-randomword-fr = "^1.0.0"
|
||||||
python-slugify = ">=3.0.2"
|
python-slugify = ">=3.0.2"
|
||||||
python-stdnum = "^1.19"
|
python-stdnum = "^1.20"
|
||||||
python-swiftclient = "3.9.0"
|
python-swiftclient = "3.9.0"
|
||||||
python-u2flib-server = "^5.0.1"
|
python-u2flib-server = "^5.0.1"
|
||||||
pytz = ">=2022.7"
|
pytz = ">=2022.7"
|
||||||
|
|
@ -143,15 +149,16 @@ pyusb = "1.0.0"
|
||||||
pyyaml = "6.0"
|
pyyaml = "6.0"
|
||||||
pyzbar = "^0.1.9"
|
pyzbar = "^0.1.9"
|
||||||
qrcode = "5.3"
|
qrcode = "5.3"
|
||||||
|
randomwords = "^0.4.0"
|
||||||
redis = "3.5.3"
|
redis = "3.5.3"
|
||||||
reportlab = "3.6.5"
|
reportlab = "3.6.5"
|
||||||
requests = "2.28.1"
|
requests = "2.28.1"
|
||||||
requests-mock = "^1.11.0"
|
requests-mock = "^1.12.1"
|
||||||
requests-oauthlib = "1.1.0"
|
requests-oauthlib = "1.1.0"
|
||||||
requests-toolbelt = "0.9.1"
|
requests-toolbelt = "0.9.1"
|
||||||
responses = "0.23.1"
|
responses = "0.23.1"
|
||||||
retrying = "^1.3.4"
|
retrying = "^1.3.4"
|
||||||
sentry-sdk = "^1.39.1"
|
sentry-sdk = "^2.8.0"
|
||||||
serial = "^0.0.97"
|
serial = "^0.0.97"
|
||||||
setuptools = "65.5.1"
|
setuptools = "65.5.1"
|
||||||
shapely = "1.8.5"
|
shapely = "1.8.5"
|
||||||
|
|
@ -159,32 +166,33 @@ simplejson = "3.16.0"
|
||||||
slugify = "^0.0.1"
|
slugify = "^0.0.1"
|
||||||
soappy = "^0.12.22"
|
soappy = "^0.12.22"
|
||||||
sphinx = "3.0"
|
sphinx = "3.0"
|
||||||
sphinx-intl = "^2.1.0"
|
sphinx-intl = "^2.2.0"
|
||||||
sphinx-rtd-theme = "1.2.2"
|
sphinx-rtd-theme = "1.2.2"
|
||||||
sqlalchemy = "^2.0.23"
|
sqlalchemy = "^2.0.31"
|
||||||
sqlparse = "^0.4.4"
|
sqlparse = "0.4.4"
|
||||||
statsd = "3.2.1"
|
statsd = "3.2.1"
|
||||||
toml = "^0.10.2"
|
toml = "^0.10.2"
|
||||||
tornado = "6.1"
|
tornado = "6.1"
|
||||||
tqdm = "^4.66.1"
|
tqdm = "^4.66.4"
|
||||||
unidecode = "1.0.22"
|
unidecode = "1.0.22"
|
||||||
unidiff = "^0.7.5"
|
unidiff = "^0.7.5"
|
||||||
urllib3 = ">=1.26.13"
|
urllib3 = ">=1.26.13"
|
||||||
uvloop = "0.17.0"
|
uvloop = "0.17.0"
|
||||||
|
validators = "0.20.0"
|
||||||
vcrpy = ">=2.1.1"
|
vcrpy = ">=2.1.1"
|
||||||
vcrpy-unittest = "^0.1.7"
|
vcrpy-unittest = "^0.1.7"
|
||||||
vobject = "0.9.3"
|
vobject = "0.9.3"
|
||||||
voicent-python = "^1.0"
|
voicent-python = "^1.0"
|
||||||
webcolors = "^1.13"
|
webcolors = "1.13"
|
||||||
websocket-client = "1.6.1"
|
websocket-client = "1.6.1"
|
||||||
werkzeug = "0.16.1"
|
werkzeug = "0.16.1"
|
||||||
wget = "^3.2"
|
wget = "^3.2"
|
||||||
wheel = "^0.42.0"
|
wheel = "0.42.0"
|
||||||
win-unicode-console = "^0.5"
|
win-unicode-console = "^0.5"
|
||||||
xlrd = "1.0.0"
|
xlrd = "1.0.0"
|
||||||
xlsxwriter = "0.9.3"
|
xlsxwriter = "0.9.3"
|
||||||
xlwt = "1.3"
|
xlwt = "1.3"
|
||||||
xmlformatter = "^0.2.4"
|
xmlformatter = "^0.2.6"
|
||||||
xmltodict = "^0.13.0"
|
xmltodict = "^0.13.0"
|
||||||
zeep = "^4.2.1"
|
zeep = "^4.2.1"
|
||||||
zipp = ">=3.6.0"
|
zipp = ">=3.6.0"
|
||||||
|
|
@ -197,9 +205,6 @@ websocket_client = "^1.1.0"
|
||||||
git = "https://github.com/psf/black.git"
|
git = "https://github.com/psf/black.git"
|
||||||
rev = "22.3.0"
|
rev = "22.3.0"
|
||||||
|
|
||||||
[tool.poetry.dependencies.pylint-odoo]
|
|
||||||
git = "https://github.com/oca/pylint-odoo.git"
|
|
||||||
|
|
||||||
[tool.poetry.dependencies.wechatpy]
|
[tool.poetry.dependencies.wechatpy]
|
||||||
extras = [ "cryptography",]
|
extras = [ "cryptography",]
|
||||||
version = "^1.8.18"
|
version = "^1.8.18"
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,9 @@ pytz>=2022.7
|
||||||
# For OCA
|
# For OCA
|
||||||
factur-x
|
factur-x
|
||||||
|
|
||||||
|
# For ERPLibre project
|
||||||
|
RandomWords
|
||||||
|
|
||||||
# For testing
|
# For testing
|
||||||
websocket-client
|
websocket-client
|
||||||
python-stdnum
|
python-stdnum
|
||||||
|
|
@ -36,7 +39,8 @@ coverage
|
||||||
# For logical test
|
# For logical test
|
||||||
docutils==0.17.1
|
docutils==0.17.1
|
||||||
flake8<6.0.0
|
flake8<6.0.0
|
||||||
git+https://github.com/oca/pylint-odoo.git
|
git+https://github.com/oca/pylint-odoo.git@v8.0.19
|
||||||
|
pylint-odoo==8.0.19
|
||||||
# oca-maintainers-tools
|
# oca-maintainers-tools
|
||||||
# git+https://github.com/OCA/maintainer-tools.git#egg=oca-maintainers-tools
|
# git+https://github.com/OCA/maintainer-tools.git#egg=oca-maintainers-tools
|
||||||
xmlformatter
|
xmlformatter
|
||||||
|
|
@ -51,6 +55,8 @@ iscompatible
|
||||||
pandas==1.3.5
|
pandas==1.3.5
|
||||||
Jinja2==2.11.3
|
Jinja2==2.11.3
|
||||||
python-keystoneclient==3.22.0
|
python-keystoneclient==3.22.0
|
||||||
|
webcolors==1.13
|
||||||
|
openpyxl==3.1.3
|
||||||
|
|
||||||
# For deployment
|
# For deployment
|
||||||
cloudflare
|
cloudflare
|
||||||
|
|
@ -65,6 +71,8 @@ shapely==1.8.5
|
||||||
|
|
||||||
# Code generator
|
# Code generator
|
||||||
mysqlclient==2.1.1
|
mysqlclient==2.1.1
|
||||||
|
python-pptx
|
||||||
|
humanize==4.6.0
|
||||||
|
|
||||||
# pyproj >=3.3.0 is not compatible with python 3.7
|
# pyproj >=3.3.0 is not compatible with python 3.7
|
||||||
pyproj==3.2.1
|
pyproj==3.2.1
|
||||||
|
|
@ -101,8 +109,8 @@ dnspython==2.3.0
|
||||||
acme==2.6.0
|
acme==2.6.0
|
||||||
altair==5.0.1
|
altair==5.0.1
|
||||||
pycountry==22.3.5
|
pycountry==22.3.5
|
||||||
email-validator==2.1.0
|
email-validator==2.0.0
|
||||||
email_validator==2.1.0
|
email_validator==2.0.0
|
||||||
sphinx-rtd-theme==1.2.2
|
sphinx-rtd-theme==1.2.2
|
||||||
sphinx_rtd_theme==1.2.2
|
sphinx_rtd_theme==1.2.2
|
||||||
uvloop==0.17.0
|
uvloop==0.17.0
|
||||||
|
|
@ -111,6 +119,16 @@ websocket-client==1.6.1
|
||||||
websocket_client==1.6.1
|
websocket_client==1.6.1
|
||||||
mmg==1.0.3
|
mmg==1.0.3
|
||||||
giturlparse==0.10.0
|
giturlparse==0.10.0
|
||||||
|
jira==2.0.0
|
||||||
|
code-writer==1.1.1
|
||||||
|
code_writer==1.1.1
|
||||||
|
wheel==0.42.0
|
||||||
|
validators==0.20.0
|
||||||
|
azure-identity==1.15.0
|
||||||
|
azure.identity==1.15.0
|
||||||
|
azure-storage-blob==12.19.0
|
||||||
|
azure.storage.blob==12.19.0
|
||||||
|
sqlparse==0.4.4
|
||||||
|
|
||||||
# Force version for security update
|
# Force version for security update
|
||||||
pyyaml==6.0
|
pyyaml==6.0
|
||||||
|
|
@ -122,7 +140,7 @@ Babel==2.9.1
|
||||||
reportlab==3.6.5
|
reportlab==3.6.5
|
||||||
urllib3>=1.26.13
|
urllib3>=1.26.13
|
||||||
requests==2.28.1
|
requests==2.28.1
|
||||||
boto3>=1.20.41
|
boto3==1.33.13
|
||||||
PyPDF2==1.27.9
|
PyPDF2==1.27.9
|
||||||
mako==1.2.2
|
mako==1.2.2
|
||||||
pyjwt==2.5.0
|
pyjwt==2.5.0
|
||||||
|
|
|
||||||
|
|
@ -156,11 +156,19 @@ def fill_search_field(ast_obj, var_name="", py_filename=""):
|
||||||
# Support -> fields.Date.context_today
|
# Support -> fields.Date.context_today
|
||||||
parent_node = ast_obj
|
parent_node = ast_obj
|
||||||
lst_call_lambda = []
|
lst_call_lambda = []
|
||||||
while hasattr(parent_node, "value"):
|
if hasattr(parent_node, "id"):
|
||||||
lst_call_lambda.insert(0, parent_node.attr)
|
while hasattr(parent_node, "value"):
|
||||||
parent_node = parent_node.value
|
lst_call_lambda.insert(0, parent_node.attr)
|
||||||
lst_call_lambda.insert(0, parent_node.id)
|
parent_node = parent_node.value
|
||||||
result = ".".join(lst_call_lambda)
|
lst_call_lambda.insert(0, parent_node.id)
|
||||||
|
result = ".".join(lst_call_lambda)
|
||||||
|
else:
|
||||||
|
# default=uuid.uuid4().hex
|
||||||
|
_logger.warning(
|
||||||
|
f"Cannot support keyword of variable {var_name} type"
|
||||||
|
f" {ast_obj_type} in filename {py_filename}, because"
|
||||||
|
" parent_node is type ast.Call."
|
||||||
|
)
|
||||||
elif ast_obj_type is ast.List:
|
elif ast_obj_type is ast.List:
|
||||||
result = [fill_search_field(a, var_name) for a in ast_obj.elts]
|
result = [fill_search_field(a, var_name) for a in ast_obj.elts]
|
||||||
elif ast_obj_type is ast.Dict:
|
elif ast_obj_type is ast.Dict:
|
||||||
|
|
|
||||||
114
script/code_generator/test_transform_python_to_code_writer.py
Normal file
114
script/code_generator/test_transform_python_to_code_writer.py
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
#!./.venv/bin/python
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
def get_config():
|
||||||
|
"""Parse command line arguments, extracting the config file name,
|
||||||
|
returning the union of config file and command line arguments
|
||||||
|
|
||||||
|
:return: dict of config file settings and command line arguments
|
||||||
|
"""
|
||||||
|
# TODO update description
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
description="""\
|
||||||
|
Transform a python file in code writer format python file, will generate it in temp file and compare difference.
|
||||||
|
""",
|
||||||
|
epilog="""\
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--debug", action="store_true", help="Show debug information."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--format", action="store_true", help="Format file before test."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-f",
|
||||||
|
"--file",
|
||||||
|
dest="file",
|
||||||
|
required=True,
|
||||||
|
help="Path of file to transform to code_writer.",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
config = get_config()
|
||||||
|
if config.format:
|
||||||
|
result = subprocess.run(
|
||||||
|
[
|
||||||
|
"bash",
|
||||||
|
"./script/maintenance/format_python.sh",
|
||||||
|
config.file,
|
||||||
|
],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
with tempfile.NamedTemporaryFile() as temp_file:
|
||||||
|
# Generate the generator
|
||||||
|
file = temp_file.name
|
||||||
|
result = subprocess.run(
|
||||||
|
[
|
||||||
|
"python",
|
||||||
|
"./script/code_generator/transform_python_to_code_writer.py",
|
||||||
|
"-f",
|
||||||
|
config.file,
|
||||||
|
"-o",
|
||||||
|
file,
|
||||||
|
],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
# if config.debug:
|
||||||
|
# print("-- show the generator --")
|
||||||
|
# print(result.stdout)
|
||||||
|
# print("-- end show the generator --")
|
||||||
|
# Execute the generator
|
||||||
|
result_generator = subprocess.run(
|
||||||
|
["python", file], capture_output=True, text=True
|
||||||
|
)
|
||||||
|
if result_generator.returncode != 0:
|
||||||
|
raise Exception(result_generator.stderr)
|
||||||
|
if config.debug:
|
||||||
|
print("-- show the generator result --")
|
||||||
|
print(result_generator.stdout)
|
||||||
|
print("-- end show the generator result --")
|
||||||
|
# Test the generator output
|
||||||
|
fd, temp_file_path = tempfile.mkstemp()
|
||||||
|
with os.fdopen(fd, "w+") as temp_file_test:
|
||||||
|
temp_file_test.write(result_generator.stdout)
|
||||||
|
temp_file_test.seek(0)
|
||||||
|
# Compare the output of the generator with original
|
||||||
|
result = subprocess.run(
|
||||||
|
["diff", config.file, temp_file_path],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
if config.debug or result.stdout:
|
||||||
|
print("-- show diff --")
|
||||||
|
print(result.stdout)
|
||||||
|
print("-- end show diff --")
|
||||||
|
if result.stdout:
|
||||||
|
print("FAIL")
|
||||||
|
result_wc = subprocess.run(
|
||||||
|
["wc", "-m", config.file], capture_output=True, text=True
|
||||||
|
)
|
||||||
|
if result_wc.returncode == 0:
|
||||||
|
line_count = int(result_wc.stdout.split()[0])
|
||||||
|
print(f"{line_count} VS {len(result_generator.stdout)}")
|
||||||
|
else:
|
||||||
|
print(f"Error with command 'wc -m {config.file}'")
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
print("PASS")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
79
script/code_generator/transform_python_to_code_writer.py
Normal file → Executable file
79
script/code_generator/transform_python_to_code_writer.py
Normal file → Executable file
|
|
@ -1,14 +1,11 @@
|
||||||
#!./.venv/bin/python
|
#!./.venv/bin/python
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import uuid
|
||||||
import os
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from code_writer import CodeWriter
|
from code_writer import CodeWriter
|
||||||
|
|
||||||
from script.git.git_tool import GitTool
|
|
||||||
|
|
||||||
# import tokenize
|
# import tokenize
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -18,8 +15,6 @@ def get_config():
|
||||||
|
|
||||||
:return: dict of config file settings and command line arguments
|
:return: dict of config file settings and command line arguments
|
||||||
"""
|
"""
|
||||||
config = GitTool.get_project_config()
|
|
||||||
|
|
||||||
# TODO update description
|
# TODO update description
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
|
@ -92,6 +87,7 @@ def main():
|
||||||
|
|
||||||
cw.emit("from code_writer import CodeWriter")
|
cw.emit("from code_writer import CodeWriter")
|
||||||
cw.emit("cw = CodeWriter()")
|
cw.emit("cw = CodeWriter()")
|
||||||
|
unique_str = f"#{uuid.uuid4().hex}#"
|
||||||
|
|
||||||
no_line = 1
|
no_line = 1
|
||||||
with open(config.file, "r") as file:
|
with open(config.file, "r") as file:
|
||||||
|
|
@ -99,7 +95,13 @@ def main():
|
||||||
nb_tab, nb_space = count_space_tab(line)
|
nb_tab, nb_space = count_space_tab(line)
|
||||||
diff_tab = nb_tab - last_nb_tab
|
diff_tab = nb_tab - last_nb_tab
|
||||||
new_line = line.strip()
|
new_line = line.strip()
|
||||||
|
|
||||||
|
# Support \, because with cw.emit("\") this will break
|
||||||
|
new_line = new_line.replace("\\", "\\\\")
|
||||||
|
# Support " into string
|
||||||
new_line = new_line.replace('"', '\\"')
|
new_line = new_line.replace('"', '\\"')
|
||||||
|
# Fix """
|
||||||
|
new_line = new_line.replace('\\"\\"\\"', unique_str)
|
||||||
|
|
||||||
status_no_tab = add_line(
|
status_no_tab = add_line(
|
||||||
cw, new_line, no_line, nb_tab, no_tab, no_tab, nb_space
|
cw, new_line, no_line, nb_tab, no_tab, no_tab, nb_space
|
||||||
|
|
@ -109,7 +111,16 @@ def main():
|
||||||
|
|
||||||
last_nb_tab = nb_tab
|
last_nb_tab = nb_tab
|
||||||
no_line += 1
|
no_line += 1
|
||||||
cw.emit("print(cw.render())")
|
argument_print = ""
|
||||||
|
if line[-1] != "\n":
|
||||||
|
# Remove last endline if missing
|
||||||
|
argument_print = ", end=''"
|
||||||
|
# Ignore last character, it's an empty space
|
||||||
|
# TODO find another technique to support """, instead of using uuid
|
||||||
|
cw.emit(
|
||||||
|
f"print(cw.render()[:-1].replace('{unique_str}',"
|
||||||
|
f' \'"""\'){argument_print})'
|
||||||
|
)
|
||||||
|
|
||||||
output = cw.render()
|
output = cw.render()
|
||||||
if config.output:
|
if config.output:
|
||||||
|
|
@ -120,7 +131,14 @@ def main():
|
||||||
|
|
||||||
|
|
||||||
def add_line(
|
def add_line(
|
||||||
cw, line, no_line, nb_indent, no_indent, init_no_intend, nb_space
|
cw,
|
||||||
|
line,
|
||||||
|
no_line,
|
||||||
|
nb_indent,
|
||||||
|
no_indent,
|
||||||
|
init_no_intend,
|
||||||
|
nb_space,
|
||||||
|
deindent=False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Recursive check indent and write line
|
Recursive check indent and write line
|
||||||
|
|
@ -139,22 +157,27 @@ def add_line(
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
if nb_indent == -1:
|
if nb_indent == -1:
|
||||||
cw.emit('cw.emit("")')
|
if no_indent > 0:
|
||||||
return 0
|
cw.emit_raw(" " * no_indent * 4 + f"cw.emit()\n")
|
||||||
|
return no_indent
|
||||||
|
else:
|
||||||
|
cw.emit("cw.emit()")
|
||||||
|
return 0
|
||||||
elif nb_indent == no_indent:
|
elif nb_indent == no_indent:
|
||||||
if nb_indent == 0:
|
if nb_indent == 0:
|
||||||
cw.emit(f'cw.emit("{line}")')
|
cw.emit(f'cw.emit("{line}")')
|
||||||
return 0
|
return 0
|
||||||
elif nb_indent == 1:
|
elif nb_indent == 1:
|
||||||
if no_indent != init_no_intend:
|
if no_indent != init_no_intend and not deindent:
|
||||||
cw.emit(f"with cw.indent({4 + nb_space if nb_space else ''}):")
|
cw.emit(f"with cw.indent({4 + nb_space if nb_space else ''}):")
|
||||||
with cw.indent():
|
with cw.indent():
|
||||||
cw.emit(f'cw.emit("{line}")')
|
cw.emit(f'cw.emit("{line}")')
|
||||||
elif nb_indent == 2:
|
elif nb_indent == 2:
|
||||||
if nb_indent - 1 > init_no_intend:
|
if nb_indent - 1 > init_no_intend:
|
||||||
cw.emit(f"with cw.indent():")
|
cw.emit(f"with cw.indent():")
|
||||||
|
# TODO support indent with cw.emit_raw
|
||||||
with cw.indent():
|
with cw.indent():
|
||||||
if no_indent != init_no_intend:
|
if no_indent != init_no_intend and not deindent:
|
||||||
cw.emit(
|
cw.emit(
|
||||||
f"with cw.indent({4 + nb_space if nb_space else ''}):"
|
f"with cw.indent({4 + nb_space if nb_space else ''}):"
|
||||||
)
|
)
|
||||||
|
|
@ -167,7 +190,7 @@ def add_line(
|
||||||
if nb_indent - 1 > init_no_intend:
|
if nb_indent - 1 > init_no_intend:
|
||||||
cw.emit(f"with cw.indent():")
|
cw.emit(f"with cw.indent():")
|
||||||
with cw.indent():
|
with cw.indent():
|
||||||
if no_indent != init_no_intend:
|
if no_indent != init_no_intend and not deindent:
|
||||||
cw.emit(
|
cw.emit(
|
||||||
"with"
|
"with"
|
||||||
f" cw.indent({4 + nb_space if nb_space else ''}):"
|
f" cw.indent({4 + nb_space if nb_space else ''}):"
|
||||||
|
|
@ -184,7 +207,7 @@ def add_line(
|
||||||
if nb_indent - 1 > init_no_intend:
|
if nb_indent - 1 > init_no_intend:
|
||||||
cw.emit(f"with cw.indent():")
|
cw.emit(f"with cw.indent():")
|
||||||
with cw.indent():
|
with cw.indent():
|
||||||
if no_indent != init_no_intend:
|
if no_indent != init_no_intend and not deindent:
|
||||||
cw.emit(
|
cw.emit(
|
||||||
"with"
|
"with"
|
||||||
f" cw.indent({4 + nb_space if nb_space else ''}):"
|
f" cw.indent({4 + nb_space if nb_space else ''}):"
|
||||||
|
|
@ -204,7 +227,7 @@ def add_line(
|
||||||
if nb_indent - 1 > init_no_intend:
|
if nb_indent - 1 > init_no_intend:
|
||||||
cw.emit(f"with cw.indent():")
|
cw.emit(f"with cw.indent():")
|
||||||
with cw.indent():
|
with cw.indent():
|
||||||
if no_indent != init_no_intend:
|
if no_indent != init_no_intend and not deindent:
|
||||||
cw.emit(
|
cw.emit(
|
||||||
"with"
|
"with"
|
||||||
f" cw.indent({4 + nb_space if nb_space else ''}):"
|
f" cw.indent({4 + nb_space if nb_space else ''}):"
|
||||||
|
|
@ -227,7 +250,10 @@ def add_line(
|
||||||
if nb_indent - 1 > init_no_intend:
|
if nb_indent - 1 > init_no_intend:
|
||||||
cw.emit(f"with cw.indent():")
|
cw.emit(f"with cw.indent():")
|
||||||
with cw.indent():
|
with cw.indent():
|
||||||
if no_indent != init_no_intend:
|
if (
|
||||||
|
no_indent != init_no_intend
|
||||||
|
and not deindent
|
||||||
|
):
|
||||||
cw.emit(
|
cw.emit(
|
||||||
"with"
|
"with"
|
||||||
f" cw.indent({4 + nb_space if nb_space else ''}):"
|
f" cw.indent({4 + nb_space if nb_space else ''}):"
|
||||||
|
|
@ -253,7 +279,10 @@ def add_line(
|
||||||
if nb_indent - 1 > init_no_intend:
|
if nb_indent - 1 > init_no_intend:
|
||||||
cw.emit(f"with cw.indent():")
|
cw.emit(f"with cw.indent():")
|
||||||
with cw.indent():
|
with cw.indent():
|
||||||
if no_indent != init_no_intend:
|
if (
|
||||||
|
no_indent != init_no_intend
|
||||||
|
and not deindent
|
||||||
|
):
|
||||||
cw.emit(
|
cw.emit(
|
||||||
"with"
|
"with"
|
||||||
f" cw.indent({4 + nb_space if nb_space else ''}):"
|
f" cw.indent({4 + nb_space if nb_space else ''}):"
|
||||||
|
|
@ -282,7 +311,10 @@ def add_line(
|
||||||
if nb_indent - 1 > init_no_intend:
|
if nb_indent - 1 > init_no_intend:
|
||||||
cw.emit(f"with cw.indent():")
|
cw.emit(f"with cw.indent():")
|
||||||
with cw.indent():
|
with cw.indent():
|
||||||
if no_indent != init_no_intend:
|
if (
|
||||||
|
no_indent != init_no_intend
|
||||||
|
and not deindent
|
||||||
|
):
|
||||||
cw.emit(
|
cw.emit(
|
||||||
"with"
|
"with"
|
||||||
f" cw.indent({4 + nb_space if nb_space else ''}):"
|
f" cw.indent({4 + nb_space if nb_space else ''}):"
|
||||||
|
|
@ -314,7 +346,10 @@ def add_line(
|
||||||
if nb_indent - 1 > init_no_intend:
|
if nb_indent - 1 > init_no_intend:
|
||||||
cw.emit(f"with cw.indent():")
|
cw.emit(f"with cw.indent():")
|
||||||
with cw.indent():
|
with cw.indent():
|
||||||
if no_indent != init_no_intend:
|
if (
|
||||||
|
no_indent != init_no_intend
|
||||||
|
and not deindent
|
||||||
|
):
|
||||||
cw.emit(
|
cw.emit(
|
||||||
"with"
|
"with"
|
||||||
f" cw.indent({4 + nb_space if nb_space else ''}):"
|
f" cw.indent({4 + nb_space if nb_space else ''}):"
|
||||||
|
|
@ -349,7 +384,10 @@ def add_line(
|
||||||
if nb_indent - 1 > init_no_intend:
|
if nb_indent - 1 > init_no_intend:
|
||||||
cw.emit(f"with cw.indent():")
|
cw.emit(f"with cw.indent():")
|
||||||
with cw.indent():
|
with cw.indent():
|
||||||
if no_indent != init_no_intend:
|
if (
|
||||||
|
no_indent != init_no_intend
|
||||||
|
and not deindent
|
||||||
|
):
|
||||||
cw.emit(
|
cw.emit(
|
||||||
"with"
|
"with"
|
||||||
f" cw.indent({4 + nb_space if nb_space else ''}):"
|
f" cw.indent({4 + nb_space if nb_space else ''}):"
|
||||||
|
|
@ -1141,6 +1179,7 @@ def add_line(
|
||||||
no_indent - 1,
|
no_indent - 1,
|
||||||
init_no_intend,
|
init_no_intend,
|
||||||
nb_space,
|
nb_space,
|
||||||
|
deindent=True,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# indent
|
# indent
|
||||||
|
|
|
||||||
|
|
@ -14,19 +14,19 @@ EL_CONFIG_FILE="${EL_HOME}/config.conf"
|
||||||
|
|
||||||
echo -e "* Create server config file"
|
echo -e "* Create server config file"
|
||||||
|
|
||||||
touch ${EL_CONFIG_FILE}
|
touch "${EL_CONFIG_FILE}"
|
||||||
echo -e "* Creating server config file"
|
echo -e "* Creating server config file"
|
||||||
printf '[options] \n; This is the password that allows database operations:\n' > ${EL_CONFIG_FILE}
|
printf '[options] \n; This is the password that allows database operations:\n' > "${EL_CONFIG_FILE}"
|
||||||
printf "admin_passwd = ${EL_SUPERADMIN}\n" >> ${EL_CONFIG_FILE}
|
printf "admin_passwd = ${EL_SUPERADMIN}\n" >> "${EL_CONFIG_FILE}"
|
||||||
printf "db_host = False\n" >> ${EL_CONFIG_FILE}
|
printf "db_host = False\n" >> "${EL_CONFIG_FILE}"
|
||||||
printf "db_port = False\n" >> ${EL_CONFIG_FILE}
|
printf "db_port = False\n" >> "${EL_CONFIG_FILE}"
|
||||||
printf "db_user = ${EL_USER}\n" >> ${EL_CONFIG_FILE}
|
printf "db_user = ${EL_USER}\n" >> "${EL_CONFIG_FILE}"
|
||||||
printf "db_password = False\n" >> ${EL_CONFIG_FILE}
|
printf "db_password = False\n" >> "${EL_CONFIG_FILE}"
|
||||||
printf "xmlrpc_port = ${EL_PORT}\n" >> ${EL_CONFIG_FILE}
|
printf "xmlrpc_port = ${EL_PORT}\n" >> "${EL_CONFIG_FILE}"
|
||||||
printf "longpolling_port = ${EL_LONGPOLLING_PORT}\n" >> ${EL_CONFIG_FILE}
|
printf "longpolling_port = ${EL_LONGPOLLING_PORT}\n" >> "${EL_CONFIG_FILE}"
|
||||||
|
|
||||||
printf "addons_path = ${EL_HOME_ODOO}/addons,${EL_HOME}/addons/addons," >> ${EL_CONFIG_FILE}
|
printf "addons_path = ${EL_HOME_ODOO}/addons,${EL_HOME}/addons/addons," >> "${EL_CONFIG_FILE}"
|
||||||
printf "${EL_HOME}/addons/OCA_web," >> ${EL_CONFIG_FILE}
|
printf "${EL_HOME}/addons/OCA_web," >> "${EL_CONFIG_FILE}"
|
||||||
if [[ ${EL_MINIMAL_ADDONS} = "False" ]]; then
|
if [[ ${EL_MINIMAL_ADDONS} = "False" ]]; then
|
||||||
printf "${EL_HOME}/addons/CybroOdoo_OpenHRMS," >> ${EL_CONFIG_FILE}
|
printf "${EL_HOME}/addons/CybroOdoo_OpenHRMS," >> ${EL_CONFIG_FILE}
|
||||||
printf "${EL_HOME}/addons/ERPLibre_erplibre-3D-printing-addons," >> ${EL_CONFIG_FILE}
|
printf "${EL_HOME}/addons/ERPLibre_erplibre-3D-printing-addons," >> ${EL_CONFIG_FILE}
|
||||||
|
|
@ -163,14 +163,14 @@ if [[ ${EL_MINIMAL_ADDONS} = "False" ]]; then
|
||||||
printf "${EL_HOME}/script/OCA_maintainer-tools," >> ${EL_CONFIG_FILE}
|
printf "${EL_HOME}/script/OCA_maintainer-tools," >> ${EL_CONFIG_FILE}
|
||||||
printf "${EL_HOME}/script/OCA_odoo-module-migrator," >> ${EL_CONFIG_FILE}
|
printf "${EL_HOME}/script/OCA_odoo-module-migrator," >> ${EL_CONFIG_FILE}
|
||||||
fi
|
fi
|
||||||
printf "\n" >> ${EL_CONFIG_FILE}
|
printf "\n" >> "${EL_CONFIG_FILE}"
|
||||||
|
|
||||||
printf "max_cron_threads = 2\n" >> ${EL_CONFIG_FILE}
|
printf "max_cron_threads = 2\n" >> "${EL_CONFIG_FILE}"
|
||||||
|
|
||||||
if [[ ${EL_INSTALL_NGINX} = "True" ]]; then
|
if [[ ${EL_INSTALL_NGINX} = "True" ]]; then
|
||||||
printf "workers = 2\n" >> ${EL_CONFIG_FILE}
|
printf "workers = 2\n" >> "${EL_CONFIG_FILE}"
|
||||||
printf "xmlrpc_interface = 127.0.0.1\n" >> ${EL_CONFIG_FILE}
|
printf "xmlrpc_interface = 127.0.0.1\n" >> "${EL_CONFIG_FILE}"
|
||||||
printf "proxy_mode = True\n" >> ${EL_CONFIG_FILE}
|
printf "proxy_mode = True\n" >> "${EL_CONFIG_FILE}"
|
||||||
else
|
else
|
||||||
printf "workers = 0\n" >> ${EL_CONFIG_FILE}
|
printf "workers = 0\n" >> "${EL_CONFIG_FILE}"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -397,7 +397,7 @@ class GitTool:
|
||||||
continue
|
continue
|
||||||
str_repo = (
|
str_repo = (
|
||||||
f' printf "${{EL_HOME}}/{repo.get("path")}," >> '
|
f' printf "${{EL_HOME}}/{repo.get("path")}," >> '
|
||||||
"${EL_CONFIG_FILE}\n"
|
'"${EL_CONFIG_FILE}"\n'
|
||||||
)
|
)
|
||||||
lst_result.append(str_repo)
|
lst_result.append(str_repo)
|
||||||
with open(filename_locally) as file:
|
with open(filename_locally) as file:
|
||||||
|
|
|
||||||
3
script/selenium/README.md
Normal file
3
script/selenium/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Installation Ubuntu 20.04
|
||||||
|
|
||||||
|
sudo apt install libcairo2-dev
|
||||||
5
script/selenium/requirements.txt
Normal file
5
script/selenium/requirements.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Selenium video
|
||||||
|
PyGObject==3.44.1
|
||||||
|
gst
|
||||||
|
# python-vlc
|
||||||
|
python-randomword-fr
|
||||||
129
script/selenium/selenium_devops.py
Executable file
129
script/selenium/selenium_devops.py
Executable file
|
|
@ -0,0 +1,129 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
import web_login
|
||||||
|
import selenium_lib
|
||||||
|
|
||||||
|
|
||||||
|
def run(config, selenium_tool):
|
||||||
|
if config.open_me_devops:
|
||||||
|
selenium_tool.click(
|
||||||
|
"/html/body/header/nav/div/div[1]/div[2]/div/div/div/ul/li[2]/a",
|
||||||
|
)
|
||||||
|
selenium_tool.click(
|
||||||
|
"/html/body/div[1]/main/div[2]/div/div/table/tbody/tr[1]/td[2]",
|
||||||
|
)
|
||||||
|
if config.open_me_devops_auto:
|
||||||
|
selenium_tool.click(
|
||||||
|
"/html/body/header/nav/div/div[1]/div[2]/div/div/div/ul/li[2]/a",
|
||||||
|
)
|
||||||
|
selenium_tool.click(
|
||||||
|
"/html/body/div[1]/main/div[2]/div/div/table/tbody/tr[1]/td[2]",
|
||||||
|
)
|
||||||
|
|
||||||
|
# CG self
|
||||||
|
# Bouton modifier
|
||||||
|
# selenium_tool.click(
|
||||||
|
# "/html/body/div[1]/main/div[1]/div[2]/div/div/div[1]/button[1]",
|
||||||
|
# )
|
||||||
|
# Tab Code
|
||||||
|
# selenium_tool.click(
|
||||||
|
# "/html/body/div[1]/main/div[2]/div/div/div[7]/ul/li[2]/a",
|
||||||
|
# )
|
||||||
|
# Bouton Plan
|
||||||
|
selenium_tool.click(
|
||||||
|
"/html/body/div[1]/main/div[2]/div/div/div[1]/div/button[1]",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Bouton autopoiesis
|
||||||
|
selenium_tool.click(
|
||||||
|
"/html/body/div[4]/div/div/main/div/div/div[5]/table[2]/tbody/tr[2]/td[1]/button",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Bouton devops regenerate
|
||||||
|
selenium_tool.click(
|
||||||
|
"/html/body/div[4]/div/div/main/div/div/div[6]/table[2]/tbody/tr[2]/td/button",
|
||||||
|
)
|
||||||
|
|
||||||
|
if config.open_me_devops_auto_force:
|
||||||
|
# Disable «Stop Execution if Env Not Clean
|
||||||
|
# selenium_tool.click(
|
||||||
|
# "/html/body/div[1]/main/div[2]/div/div/div[7]/div/div[2]/div[6]/table[2]/tbody/tr[3]/td[2]/div/label",
|
||||||
|
# )
|
||||||
|
# Option Force Generate
|
||||||
|
selenium_tool.click(
|
||||||
|
"/html/body/div[4]/div/div/main/div/div/div[8]/table/tbody/tr[2]/td[1]/label",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Gen
|
||||||
|
# selenium_tool.click(
|
||||||
|
# "/html/body/div[1]/main/div[2]/div/div/div[7]/div/div[2]/div[2]/table[1]/tbody/tr/td[1]/button",
|
||||||
|
# )
|
||||||
|
|
||||||
|
# Next state
|
||||||
|
selenium_tool.click(
|
||||||
|
"/html/body/div[4]/div/div/footer/div/footer/div/button[1]",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check error
|
||||||
|
# selenium_tool.click(
|
||||||
|
# "/html/body/div[1]/main/div[2]/div/div/div[7]/ul/li[13]/a",
|
||||||
|
# )
|
||||||
|
# selenium_tool.click("/html/body/div[1]/main/div[2]/div/div/div[6]/div/div[14]/div/div[2]/table/tbody/tr[1]", timeout=60*2)
|
||||||
|
|
||||||
|
|
||||||
|
def fill_parser(parser):
|
||||||
|
group_devops = parser.add_argument_group(title="DevOps")
|
||||||
|
group_devops.add_argument(
|
||||||
|
"--open_me_devops",
|
||||||
|
action="store_true",
|
||||||
|
help="Open application devops and show entry me.",
|
||||||
|
)
|
||||||
|
group_devops.add_argument(
|
||||||
|
"--open_me_devops_auto",
|
||||||
|
action="store_true",
|
||||||
|
help="Open application devops and show entry me and run auto setup.",
|
||||||
|
)
|
||||||
|
group_devops.add_argument(
|
||||||
|
"--open_me_devops_auto_force",
|
||||||
|
action="store_true",
|
||||||
|
help=(
|
||||||
|
"Open application devops and show entry me and run auto setup with"
|
||||||
|
" forcing argument."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def compute_args(args):
|
||||||
|
if args.open_me_devops_auto_force:
|
||||||
|
args.open_me_devops_auto = True
|
||||||
|
args.open_me_devops = True
|
||||||
|
elif args.open_me_devops_auto:
|
||||||
|
args.open_me_devops = True
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
description="""Selenium script to open web browser to ERPLibre adapted for ORE.""",
|
||||||
|
)
|
||||||
|
# Generate parser
|
||||||
|
selenium_lib.fill_parser(parser)
|
||||||
|
web_login.fill_parser(parser)
|
||||||
|
fill_parser(parser)
|
||||||
|
args = parser.parse_args()
|
||||||
|
web_login.compute_args(args)
|
||||||
|
compute_args(args)
|
||||||
|
# Instance selenium tool
|
||||||
|
selenium_tool = selenium_lib.SeleniumLib(args)
|
||||||
|
selenium_tool.configure()
|
||||||
|
selenium_tool.start_record()
|
||||||
|
# Execute
|
||||||
|
web_login.run(args, selenium_tool)
|
||||||
|
run(args, selenium_tool)
|
||||||
|
selenium_tool.stop_record()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
450
script/selenium/selenium_lib.py
Normal file
450
script/selenium/selenium_lib.py
Normal file
|
|
@ -0,0 +1,450 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
from randomwordfr import RandomWordFr
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.support import expected_conditions as EC
|
||||||
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
|
from selenium.webdriver.firefox.service import Service
|
||||||
|
from subprocess import getoutput
|
||||||
|
from selenium.webdriver import ActionChains
|
||||||
|
from selenium.webdriver.common.actions.wheel_input import ScrollOrigin
|
||||||
|
|
||||||
|
|
||||||
|
class SeleniumLib(object):
|
||||||
|
def __init__(self, config):
|
||||||
|
self.config = config
|
||||||
|
self.video_recorder = None
|
||||||
|
self.filename = "video_" + time.strftime("%Y_%m_%d-%H_%M_%S") + ".webm"
|
||||||
|
self.driver = None
|
||||||
|
|
||||||
|
def configure(self):
|
||||||
|
# Configuration pour lancer Firefox en mode de navigation privée
|
||||||
|
firefox_options = webdriver.FirefoxOptions()
|
||||||
|
if not self.config.not_private_mode:
|
||||||
|
firefox_options.add_argument("--private")
|
||||||
|
|
||||||
|
# firefox_options.set_preference("browser.link.open_newwindow", 3)
|
||||||
|
# firefox_options.set_preference("browser.link.open_newwindow.restriction", 2)
|
||||||
|
|
||||||
|
# Définissez la préférence pour les notifications de bureau
|
||||||
|
# 1 signifie "autoriser", 2 signifie "bloquer"
|
||||||
|
firefox_options.set_preference(
|
||||||
|
"permissions.default.desktop-notification", 1
|
||||||
|
)
|
||||||
|
firefox_services = None
|
||||||
|
if self.config.firefox_binary_path:
|
||||||
|
firefox_services = Service(
|
||||||
|
executable_path=self.config.firefox_binary_path
|
||||||
|
)
|
||||||
|
if self.config.gecko_binary_path:
|
||||||
|
firefox_options.binary_location = self.config.gecko_binary_path
|
||||||
|
|
||||||
|
# Créez une instance du navigateur Firefox avec les options de navigation privée
|
||||||
|
try:
|
||||||
|
self.driver = webdriver.Firefox(
|
||||||
|
options=firefox_options, service=firefox_services
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
print(
|
||||||
|
"Cannot open Firefox profile, so will force firefox snap for"
|
||||||
|
" Ubuntu users."
|
||||||
|
)
|
||||||
|
firefox_services = Service(
|
||||||
|
executable_path=getoutput(
|
||||||
|
"find /snap/firefox -name geckodriver"
|
||||||
|
).split("\n")[-1]
|
||||||
|
)
|
||||||
|
firefox_options.binary_location = getoutput(
|
||||||
|
"find /snap/firefox -name firefox"
|
||||||
|
).split("\n")[-1]
|
||||||
|
self.driver = webdriver.Firefox(
|
||||||
|
options=firefox_options, service=firefox_services
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ajout de l'enregistrement
|
||||||
|
if self.config.record_mode:
|
||||||
|
new_path = os.path.normpath(
|
||||||
|
os.path.join(os.path.dirname(__file__), "..", "..")
|
||||||
|
)
|
||||||
|
sys.path.append(new_path)
|
||||||
|
from script.selenium.selenium_video import VideoRecorder
|
||||||
|
|
||||||
|
self.video_recorder = VideoRecorder(
|
||||||
|
self.driver, filename=self.filename
|
||||||
|
)
|
||||||
|
# import vlc
|
||||||
|
|
||||||
|
# Install DarkReader to help my eyes
|
||||||
|
# TODO do a script to check if it's the last version
|
||||||
|
if not self.config.no_dark_mode:
|
||||||
|
self.driver.install_addon(
|
||||||
|
"./script/selenium/darkreader-firefox.xpi", temporary=True
|
||||||
|
)
|
||||||
|
self.driver.install_addon(
|
||||||
|
"./script/selenium/odoo_debug-4.0.xpi", temporary=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.config.not_private_mode and not self.config.no_dark_mode:
|
||||||
|
self.driver.get("about:addons")
|
||||||
|
# Enable Dark Reader into incognito
|
||||||
|
self.click("/html/body/div/div[1]/categories-box/button[2]")
|
||||||
|
self.click(
|
||||||
|
"/html/body/div/div[2]/div/addon-list/section[1]/addon-card/div/div/div/div/button",
|
||||||
|
)
|
||||||
|
self.click(
|
||||||
|
"/html/body/div/div[2]/div/addon-list/section[1]/addon-card/div/addon-options/panel-list/panel-item[5]",
|
||||||
|
)
|
||||||
|
self.click(
|
||||||
|
"/html/body/div/div[2]/div/addon-card/div/addon-details/named-deck/section/div[5]/div/label[1]/input",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Enable Odoo_debug into incognito
|
||||||
|
# Back
|
||||||
|
self.click(
|
||||||
|
"/html/body/div/div[2]/addon-page-header/div/div[2]/button"
|
||||||
|
)
|
||||||
|
self.click(
|
||||||
|
"/html/body/div/div[2]/div/addon-list/section[1]/addon-card[2]/div/div/div/div/button",
|
||||||
|
)
|
||||||
|
self.click(
|
||||||
|
"/html/body/div/div[2]/div/addon-list/section[1]/addon-card[2]/div/addon-options/panel-list/panel-item[5]",
|
||||||
|
)
|
||||||
|
self.click(
|
||||||
|
"/html/body/div/div[2]/div/addon-card/div/addon-details/named-deck/section/div[5]/div/label[1]/input",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ouvrez la page web
|
||||||
|
self.driver.get(f"{self.config.url}/web")
|
||||||
|
|
||||||
|
# Close tab opening by DarkReader
|
||||||
|
if self.config.not_private_mode and not self.config.no_dark_mode:
|
||||||
|
self.driver.switch_to.window(self.driver.window_handles[-1])
|
||||||
|
self.driver.close()
|
||||||
|
self.driver.switch_to.window(self.driver.window_handles[0])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_french_word_no_space_no_accent():
|
||||||
|
word = ""
|
||||||
|
rw = RandomWordFr()
|
||||||
|
while not word or " " in word:
|
||||||
|
word = rw.get().get("word")
|
||||||
|
if not re.match("^[a-zA-Z_]+$", word):
|
||||||
|
word = ""
|
||||||
|
return word
|
||||||
|
|
||||||
|
def click(self, xpath, timeout=5):
|
||||||
|
wait = WebDriverWait(self.driver, timeout)
|
||||||
|
button = wait.until(
|
||||||
|
EC.element_to_be_clickable(
|
||||||
|
(
|
||||||
|
By.XPATH,
|
||||||
|
xpath,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
button.click()
|
||||||
|
|
||||||
|
def get_element(self, by: str = By.ID, value: str = None, timeout=5):
|
||||||
|
wait = WebDriverWait(self.driver, timeout)
|
||||||
|
ele = wait.until(
|
||||||
|
EC.visibility_of_any_elements_located(
|
||||||
|
(
|
||||||
|
by,
|
||||||
|
value,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return ele[0]
|
||||||
|
|
||||||
|
def click_with_mouse_move(
|
||||||
|
self,
|
||||||
|
by: str = By.ID,
|
||||||
|
value: str = None,
|
||||||
|
timeout: int = 5,
|
||||||
|
no_scroll: bool = False,
|
||||||
|
viewport_ele_by: str = By.ID,
|
||||||
|
viewport_ele_value: str = None,
|
||||||
|
):
|
||||||
|
# ele = self.driver.find_element(by, value)
|
||||||
|
ele = self.get_element(by, value, timeout)
|
||||||
|
if not no_scroll:
|
||||||
|
viewport_ele = None
|
||||||
|
if viewport_ele_value:
|
||||||
|
viewport_ele = self.get_element(
|
||||||
|
viewport_ele_by, viewport_ele_value, timeout
|
||||||
|
)
|
||||||
|
self.scrollto_element(ele, viewport_ele=viewport_ele)
|
||||||
|
ActionChains(self.driver).move_to_element(ele).perform()
|
||||||
|
time.sleep(self.config.selenium_default_delay)
|
||||||
|
wait = WebDriverWait(self.driver, timeout)
|
||||||
|
button = wait.until(
|
||||||
|
EC.element_to_be_clickable(
|
||||||
|
(
|
||||||
|
by,
|
||||||
|
value,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
button.click()
|
||||||
|
|
||||||
|
def input_text_with_mouse_move(
|
||||||
|
self,
|
||||||
|
by: str = By.ID,
|
||||||
|
value: str = None,
|
||||||
|
text_value: str = "",
|
||||||
|
delay_after: int = -1,
|
||||||
|
timeout: int = 5,
|
||||||
|
no_scroll: bool = False,
|
||||||
|
viewport_ele_by: str = By.ID,
|
||||||
|
viewport_ele_value: str = None,
|
||||||
|
):
|
||||||
|
# ele = self.driver.find_element(by, value)
|
||||||
|
ele = self.get_element(by, value)
|
||||||
|
if not no_scroll:
|
||||||
|
viewport_ele = None
|
||||||
|
if viewport_ele_value:
|
||||||
|
viewport_ele = self.get_element(
|
||||||
|
viewport_ele_by, viewport_ele_value, timeout
|
||||||
|
)
|
||||||
|
self.scrollto_element(ele, viewport_ele=viewport_ele)
|
||||||
|
ActionChains(self.driver).move_to_element(ele).perform()
|
||||||
|
ele.send_keys(text_value)
|
||||||
|
if delay_after > 0:
|
||||||
|
time.sleep(delay_after)
|
||||||
|
elif self.config.selenium_default_delay > 0:
|
||||||
|
time.sleep(self.config.selenium_default_delay)
|
||||||
|
|
||||||
|
def scrollto_element1(self, element_name):
|
||||||
|
# ele = self.driver.find_element(By.TAG_NAME, element_name)
|
||||||
|
ele = self.get_element(By.TAG_NAME, element_name)
|
||||||
|
# delta_y = ele.rect["y"]
|
||||||
|
ActionChains(self.driver).scroll_to_element(ele).perform()
|
||||||
|
# ele = self.get_element(By.TAG_NAME, element_name)
|
||||||
|
# delta_y = ele.rect["y"]
|
||||||
|
# ActionChains(self.driver).scroll_by_amount(0, delta_y).perform()
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
def scrollto_xpath5(self, xpath):
|
||||||
|
footer = self.driver.find_element(By.XPATH, xpath)
|
||||||
|
# footer = self.get_element(By.XPATH, xpath)
|
||||||
|
delta_y = footer.rect["y"]
|
||||||
|
ActionChains(self.driver).scroll_by_amount(0, int(delta_y)).perform()
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
def scrollto_xpath2(self, xpath):
|
||||||
|
footer = self.driver.find_element(By.XPATH, xpath)
|
||||||
|
# footer = self.get_element(By.XPATH, xpath)
|
||||||
|
scroll_origin = ScrollOrigin.from_element(footer)
|
||||||
|
ActionChains(self.driver).scroll_from_origin(
|
||||||
|
scroll_origin, 0, 100
|
||||||
|
).perform()
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
def scrollto_xpath3(self, xpath):
|
||||||
|
footer = self.driver.find_element(By.XPATH, xpath)
|
||||||
|
# footer = self.get_element(By.XPATH, xpath)
|
||||||
|
scroll_origin = ScrollOrigin.from_element(footer)
|
||||||
|
ActionChains(self.driver).scroll_to_element(scroll_origin).perform()
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
def scrollto_xpath(
|
||||||
|
self,
|
||||||
|
xpath,
|
||||||
|
offset_middle=True,
|
||||||
|
smooth_scroll=True,
|
||||||
|
smooth_speed=50,
|
||||||
|
smooth_fps=1 / 25,
|
||||||
|
):
|
||||||
|
footer = self.driver.find_element(By.XPATH, xpath)
|
||||||
|
# footer = self.get_element(By.XPATH, xpath)
|
||||||
|
scroll_origin = ScrollOrigin.from_viewport(10, 10)
|
||||||
|
delta_y = footer.rect["y"]
|
||||||
|
if offset_middle:
|
||||||
|
# TODO check size windows and divide by 2
|
||||||
|
delta_y -= 400
|
||||||
|
print(f"scroll to xpath at y:{delta_y} position")
|
||||||
|
if smooth_scroll:
|
||||||
|
actual_pos = 0
|
||||||
|
while delta_y > actual_pos:
|
||||||
|
actual_pos += smooth_speed
|
||||||
|
print(actual_pos)
|
||||||
|
ActionChains(self.driver).scroll_from_origin(
|
||||||
|
scroll_origin, 0, int(smooth_speed)
|
||||||
|
).perform()
|
||||||
|
print(f"Wait {smooth_fps} seconds")
|
||||||
|
time.sleep(smooth_fps)
|
||||||
|
print("end")
|
||||||
|
else:
|
||||||
|
ActionChains(self.driver).scroll_from_origin(
|
||||||
|
scroll_origin, 0, int(delta_y)
|
||||||
|
).perform()
|
||||||
|
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
def scrollto_element(
|
||||||
|
self,
|
||||||
|
element,
|
||||||
|
offset_middle=True,
|
||||||
|
smooth_scroll=True,
|
||||||
|
smooth_speed=50,
|
||||||
|
smooth_fps=1 / 25,
|
||||||
|
viewport_ele=None,
|
||||||
|
):
|
||||||
|
if viewport_ele:
|
||||||
|
scroll_origin = ScrollOrigin.from_element(viewport_ele, 10, 10)
|
||||||
|
else:
|
||||||
|
scroll_origin = ScrollOrigin.from_viewport(10, 10)
|
||||||
|
delta_y = element.rect["y"]
|
||||||
|
if offset_middle:
|
||||||
|
# TODO check size windows and divide by 2
|
||||||
|
delta_y -= 400
|
||||||
|
print(f"scroll to xpath at y:{delta_y} position")
|
||||||
|
if smooth_scroll:
|
||||||
|
actual_pos = 0
|
||||||
|
while delta_y > actual_pos:
|
||||||
|
actual_pos += smooth_speed
|
||||||
|
print(actual_pos)
|
||||||
|
ActionChains(self.driver).scroll_from_origin(
|
||||||
|
scroll_origin, 0, int(smooth_speed)
|
||||||
|
).perform()
|
||||||
|
print(f"Wait {smooth_fps} seconds")
|
||||||
|
time.sleep(smooth_fps)
|
||||||
|
print("end")
|
||||||
|
else:
|
||||||
|
ActionChains(self.driver).scroll_from_origin(
|
||||||
|
scroll_origin, 0, int(delta_y)
|
||||||
|
).perform()
|
||||||
|
|
||||||
|
# action = ActionChains(self.driver)
|
||||||
|
# action.click_and_hold(element).move_by_offset(0, 1).perform()
|
||||||
|
# # Don't forget to release the click_and_hold, for example with:
|
||||||
|
# action.reset_actions()
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
def inject_cursor(self):
|
||||||
|
cursor_script = """
|
||||||
|
var cursor = document.createElement('div');
|
||||||
|
cursor.style.position = 'absolute';
|
||||||
|
cursor.style.zIndex = '9999';
|
||||||
|
cursor.style.width = '30px';
|
||||||
|
cursor.style.height = '30px';
|
||||||
|
cursor.style.borderRadius = '50%';
|
||||||
|
cursor.style.backgroundColor = 'red';
|
||||||
|
cursor.style.pointerEvents = 'none';
|
||||||
|
document.body.appendChild(cursor);
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', function(e) {
|
||||||
|
cursor.style.left = e.pageX - 5 + 'px';
|
||||||
|
cursor.style.top = e.pageY - 5 + 'px';
|
||||||
|
});
|
||||||
|
"""
|
||||||
|
self.driver.execute_script(cursor_script)
|
||||||
|
|
||||||
|
def open_tab(self, url):
|
||||||
|
# Open a new window
|
||||||
|
self.driver.execute_script("window.open('');")
|
||||||
|
# Switch to the new window and open new URL
|
||||||
|
self.driver.switch_to.window(self.driver.window_handles[1])
|
||||||
|
self.driver.get(url)
|
||||||
|
|
||||||
|
def check_bot_chat_and_close(self):
|
||||||
|
try:
|
||||||
|
xpath_button = "/html/body/div[3]/div[1]/span[2]/a[2]"
|
||||||
|
button = self.driver.find_element(By.XPATH, xpath_button)
|
||||||
|
# button = self.get_element(By.XPATH, xpath_button)
|
||||||
|
self.click(self.driver, xpath_button)
|
||||||
|
except Exception:
|
||||||
|
print("Chatbot cannot be found, stop searching it.")
|
||||||
|
|
||||||
|
def start_record(self):
|
||||||
|
# Démarrer l'enregistrement
|
||||||
|
if self.config.record_mode:
|
||||||
|
self.video_recorder.start()
|
||||||
|
|
||||||
|
def stop_record(self):
|
||||||
|
time.sleep(self.config.selenium_default_delay_presentation)
|
||||||
|
|
||||||
|
# Arrêter l'enregistrement
|
||||||
|
if self.config.record_mode:
|
||||||
|
time.sleep(self.config.selenium_video_time_waiting_end)
|
||||||
|
self.video_recorder.stop()
|
||||||
|
print("End of recording video")
|
||||||
|
|
||||||
|
if self.config.selenium_video_auto_play_video:
|
||||||
|
print(f"Play video {self.filename}")
|
||||||
|
# By python-vlc
|
||||||
|
# media_player = vlc.MediaPlayer(filename)
|
||||||
|
# media = vlc.Media(filename)
|
||||||
|
# media_player.set_media(media)
|
||||||
|
# media_player.play()
|
||||||
|
# time.sleep(15)
|
||||||
|
# By external process
|
||||||
|
os.popen(f"vlc {self.filename}")
|
||||||
|
|
||||||
|
|
||||||
|
def fill_parser(parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"--url",
|
||||||
|
default="http://127.0.0.1:8069",
|
||||||
|
help="First URL to open.",
|
||||||
|
)
|
||||||
|
|
||||||
|
group_animation = parser.add_argument_group(title="Animation")
|
||||||
|
group_animation.add_argument(
|
||||||
|
"--selenium_default_delay",
|
||||||
|
default=0.4,
|
||||||
|
type=float,
|
||||||
|
help="Pause at the end before close video.",
|
||||||
|
)
|
||||||
|
|
||||||
|
group_browser = parser.add_argument_group(title="Browser")
|
||||||
|
group_browser.add_argument(
|
||||||
|
"--not_private_mode",
|
||||||
|
action="store_true",
|
||||||
|
help="Default is private mode.",
|
||||||
|
)
|
||||||
|
group_browser.add_argument(
|
||||||
|
"--no_dark_mode",
|
||||||
|
action="store_true",
|
||||||
|
help=(
|
||||||
|
"By default, will be in dark mode, because the main developer"
|
||||||
|
" like it!"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
group_browser.add_argument(
|
||||||
|
"--firefox_binary_path",
|
||||||
|
help="Can specify firefox path to open selenium.",
|
||||||
|
)
|
||||||
|
group_browser.add_argument(
|
||||||
|
"--gecko_binary_path",
|
||||||
|
help="Can specify firefox path to open selenium.",
|
||||||
|
)
|
||||||
|
|
||||||
|
group_record = parser.add_argument_group(title="Recording")
|
||||||
|
group_record.add_argument(
|
||||||
|
"--record_mode",
|
||||||
|
action="store_true",
|
||||||
|
help="Start recording (not finish to be implemented).",
|
||||||
|
)
|
||||||
|
group_record.add_argument(
|
||||||
|
"--selenium_video_time_waiting_end",
|
||||||
|
default=5,
|
||||||
|
type=int,
|
||||||
|
help="Pause at the end before close video.",
|
||||||
|
)
|
||||||
|
group_record.add_argument(
|
||||||
|
"--selenium_default_delay_presentation",
|
||||||
|
default=2,
|
||||||
|
type=float,
|
||||||
|
help="Pause at the end before close video.",
|
||||||
|
)
|
||||||
|
group_record.add_argument(
|
||||||
|
"--selenium_video_auto_play_video",
|
||||||
|
action="store_true",
|
||||||
|
help="Autoplay the video when done.",
|
||||||
|
)
|
||||||
|
|
@ -5,237 +5,80 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from selenium import webdriver
|
import selenium_lib
|
||||||
|
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.support import expected_conditions as EC
|
|
||||||
from selenium.webdriver.support.ui import WebDriverWait
|
|
||||||
from selenium.common.exceptions import NoSuchElementException
|
|
||||||
from selenium.webdriver.firefox.service import Service
|
|
||||||
from subprocess import getoutput
|
|
||||||
|
|
||||||
|
|
||||||
def get_config():
|
def fill_parser(parser):
|
||||||
parser = argparse.ArgumentParser(
|
group_login = parser.add_argument_group(title="Login")
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
group_login.add_argument(
|
||||||
description="""Selenium script to open web browser to ERPLibre.""",
|
"--open_dashboard",
|
||||||
epilog="""\
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--open_me_devops",
|
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Open application devops and show entry me.",
|
help="Open application Board.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
group_login.add_argument(
|
||||||
"--open_me_devops_auto",
|
"--default_email_auth",
|
||||||
action="store_true",
|
default="admin",
|
||||||
help="Open application devops and show entry me and run auto setup.",
|
help="Email to use to authenticate with admin.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
group_login.add_argument(
|
||||||
"--open_me_devops_auto_force",
|
"--default_password_auth",
|
||||||
action="store_true",
|
default="admin",
|
||||||
help=(
|
help="Password to use to authenticate with admin.",
|
||||||
"Open application devops and show entry me and run auto setup with"
|
|
||||||
" forcing argument."
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--not_private_mode",
|
|
||||||
action="store_true",
|
|
||||||
help="Default is private mode.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--record_mode",
|
|
||||||
action="store_true",
|
|
||||||
help="Start recording (not finish to be implemented).",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--not_dark_mode",
|
|
||||||
action="store_true",
|
|
||||||
help=(
|
|
||||||
"By default, will be in dark mode, because the main developer"
|
|
||||||
" like it!"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--firefox_binary_path",
|
|
||||||
help="Can specify firefox path to open selenium.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--gecko_binary_path",
|
|
||||||
help="Can specify firefox path to open selenium.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--url",
|
|
||||||
default="http://127.0.0.1:8069",
|
|
||||||
help="URL to open.",
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
if args.open_me_devops_auto_force:
|
|
||||||
args.open_me_devops_auto = True
|
|
||||||
args.open_me_devops = True
|
|
||||||
elif args.open_me_devops_auto:
|
|
||||||
args.open_me_devops = True
|
|
||||||
return args
|
|
||||||
|
|
||||||
|
|
||||||
def click(driver, xpath, time=5):
|
def run(config, selenium_tool):
|
||||||
wait = WebDriverWait(driver, time)
|
|
||||||
button = wait.until(
|
|
||||||
EC.element_to_be_clickable(
|
|
||||||
(
|
|
||||||
By.XPATH,
|
|
||||||
xpath,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
button.click()
|
|
||||||
|
|
||||||
|
|
||||||
def check_bot_chat_and_close(driver):
|
|
||||||
try:
|
|
||||||
xpath_button = "/html/body/div[3]/div[1]/span[2]/a[2]"
|
|
||||||
button = driver.find_element(By.XPATH, xpath_button)
|
|
||||||
click(driver, xpath_button)
|
|
||||||
except Exception:
|
|
||||||
print("element not found")
|
|
||||||
|
|
||||||
|
|
||||||
def run(config):
|
|
||||||
if config.record_mode:
|
|
||||||
new_path = os.path.normpath(
|
|
||||||
os.path.join(os.path.dirname(__file__), "..", "..")
|
|
||||||
)
|
|
||||||
sys.path.append(new_path)
|
|
||||||
from script.selenium.selenium_video import VideoRecorder
|
|
||||||
|
|
||||||
# Configuration pour lancer Firefox en mode de navigation privée
|
|
||||||
firefox_options = webdriver.FirefoxOptions()
|
|
||||||
if not config.not_private_mode:
|
|
||||||
firefox_options.add_argument("--private")
|
|
||||||
|
|
||||||
# Définissez la préférence pour les notifications de bureau
|
|
||||||
# 1 signifie "autoriser", 2 signifie "bloquer"
|
|
||||||
firefox_options.set_preference(
|
|
||||||
"permissions.default.desktop-notification", 1
|
|
||||||
)
|
|
||||||
firefox_services = None
|
|
||||||
if config.firefox_binary_path:
|
|
||||||
firefox_services = Service(executable_path=config.firefox_binary_path)
|
|
||||||
if config.gecko_binary_path:
|
|
||||||
firefox_options.binary_location = config.gecko_binary_path
|
|
||||||
|
|
||||||
# Créez une instance du navigateur Firefox avec les options de navigation privée
|
|
||||||
try:
|
|
||||||
driver = webdriver.Firefox(
|
|
||||||
options=firefox_options, service=firefox_services
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
print(
|
|
||||||
"Cannot open Firefox profile, so will force firefox snap for"
|
|
||||||
" Ubuntu users."
|
|
||||||
)
|
|
||||||
firefox_services = Service(
|
|
||||||
executable_path=getoutput(
|
|
||||||
"find /snap/firefox -name geckodriver"
|
|
||||||
).split("\n")[-1]
|
|
||||||
)
|
|
||||||
firefox_options.binary_location = getoutput(
|
|
||||||
"find /snap/firefox -name firefox"
|
|
||||||
).split("\n")[-1]
|
|
||||||
driver = webdriver.Firefox(
|
|
||||||
options=firefox_options, service=firefox_services
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ajout de l'enregistrement
|
|
||||||
if config.record_mode:
|
|
||||||
video_recorder = VideoRecorder(driver)
|
|
||||||
else:
|
|
||||||
video_recorder = None
|
|
||||||
|
|
||||||
# Install DarkReader to help my eyes
|
|
||||||
# TODO do a script to check if it's the last version
|
|
||||||
if not config.not_dark_mode:
|
|
||||||
driver.install_addon(
|
|
||||||
"./script/selenium/darkreader-firefox.xpi", temporary=True
|
|
||||||
)
|
|
||||||
driver.install_addon(
|
|
||||||
"./script/selenium/odoo_debug-4.0.xpi", temporary=True
|
|
||||||
)
|
|
||||||
|
|
||||||
if not config.not_private_mode and not config.not_dark_mode:
|
|
||||||
driver.get("about:addons")
|
|
||||||
# Enable Dark Reader into incognito
|
|
||||||
click(driver, "/html/body/div/div[1]/categories-box/button[2]")
|
|
||||||
click(
|
|
||||||
driver,
|
|
||||||
"/html/body/div/div[2]/div/addon-list/section[1]/addon-card/div/div/div/div/button",
|
|
||||||
)
|
|
||||||
click(
|
|
||||||
driver,
|
|
||||||
"/html/body/div/div[2]/div/addon-list/section[1]/addon-card/div/addon-options/panel-list/panel-item[5]",
|
|
||||||
)
|
|
||||||
click(
|
|
||||||
driver,
|
|
||||||
"/html/body/div/div[2]/div/addon-card/div/addon-details/named-deck/section/div[5]/div/label[1]/input",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Enable Odoo_debug into incognito
|
|
||||||
# Back
|
|
||||||
click(
|
|
||||||
driver, "/html/body/div/div[2]/addon-page-header/div/div[2]/button"
|
|
||||||
)
|
|
||||||
click(
|
|
||||||
driver,
|
|
||||||
"/html/body/div/div[2]/div/addon-list/section[1]/addon-card[2]/div/div/div/div/button",
|
|
||||||
)
|
|
||||||
click(
|
|
||||||
driver,
|
|
||||||
"/html/body/div/div[2]/div/addon-list/section[1]/addon-card[2]/div/addon-options/panel-list/panel-item[5]",
|
|
||||||
)
|
|
||||||
click(
|
|
||||||
driver,
|
|
||||||
"/html/body/div/div[2]/div/addon-card/div/addon-details/named-deck/section/div[5]/div/label[1]/input",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ouvrez la page web
|
|
||||||
driver.get(f"{config.url}/web")
|
|
||||||
|
|
||||||
# Close tab opening by DarkReader
|
|
||||||
if config.not_private_mode and not config.not_dark_mode:
|
|
||||||
driver.switch_to.window(driver.window_handles[-1])
|
|
||||||
driver.close()
|
|
||||||
driver.switch_to.window(driver.window_handles[0])
|
|
||||||
|
|
||||||
# Démarrer l'enregistrement
|
|
||||||
if config.record_mode:
|
|
||||||
video_recorder.start()
|
|
||||||
|
|
||||||
# Trouvez les éléments du formulaire
|
# Trouvez les éléments du formulaire
|
||||||
courriel_input = driver.find_element(By.NAME, "login")
|
courriel_input = selenium_tool.driver.find_element(By.NAME, "login")
|
||||||
mot_de_passe_input = driver.find_element(By.NAME, "password")
|
mot_de_passe_input = selenium_tool.driver.find_element(By.NAME, "password")
|
||||||
try:
|
try:
|
||||||
connexion_button = driver.find_element(
|
connexion_button = selenium_tool.driver.find_element(
|
||||||
By.XPATH,
|
By.XPATH,
|
||||||
"/html/body/div/div/div/form/div[3]/button"
|
"/html/body/div/div/div/form/div[3]/button"
|
||||||
# '//button[contains(text(), "Log in")]'
|
# '//button[contains(text(), "Log in")]'
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
connexion_button = driver.find_element(
|
connexion_button = selenium_tool.driver.find_element(
|
||||||
By.XPATH,
|
By.XPATH,
|
||||||
"/html/body/div/main/div/form/div[3]/button"
|
"/html/body/div/main/div/form/div[3]/button"
|
||||||
# '//button[contains(text(), "Connexion")]'
|
# '//button[contains(text(), "Connexion")]'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Remplissez le courriel et le mot de passe
|
# Remplissez le courriel et le mot de passe
|
||||||
courriel_input.send_keys("admin")
|
courriel_input.send_keys(config.default_email_auth)
|
||||||
mot_de_passe_input.send_keys("admin")
|
mot_de_passe_input.send_keys(config.default_password_auth)
|
||||||
|
|
||||||
# Cliquez sur le bouton "Connexion"
|
# Cliquez sur le bouton "Connexion"
|
||||||
connexion_button.click()
|
try:
|
||||||
|
connexion_button.click()
|
||||||
|
except Exception as e:
|
||||||
|
if (
|
||||||
|
'class="modal o_technical_modal show modal_shown"> obscures it'
|
||||||
|
in str(e)
|
||||||
|
):
|
||||||
|
# Click it
|
||||||
|
# xpath_button = "//div[hasclass('o_technical_modal')]/div/div/footer/button"
|
||||||
|
xpath_button = (
|
||||||
|
"//div[contains(@class,"
|
||||||
|
" 'o_technical_modal')]/div/div/footer/button"
|
||||||
|
)
|
||||||
|
error_button = selenium_tool.driver.find_element(
|
||||||
|
By.XPATH, xpath_button
|
||||||
|
)
|
||||||
|
error_button.click()
|
||||||
|
|
||||||
|
# Remplissez le courriel et le mot de passe
|
||||||
|
courriel_input.send_keys(config.default_email_auth)
|
||||||
|
mot_de_passe_input.send_keys(config.default_password_auth)
|
||||||
|
|
||||||
|
connexion_button.click()
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
# Attendez que l'élément soit cliquable
|
# Attendez que l'élément soit cliquable
|
||||||
# wait = WebDriverWait(driver, 5)
|
# wait = WebDriverWait(selenium_tool.driver, 5)
|
||||||
# menu_toggle = wait.until(
|
# menu_toggle = wait.until(
|
||||||
# EC.element_to_be_clickable((By.XPATH, "/html/body/header/nav/ul[1]/li/a"))
|
# EC.element_to_be_clickable((By.XPATH, "/html/body/header/nav/ul[1]/li/a"))
|
||||||
# )
|
# )
|
||||||
|
|
@ -253,107 +96,50 @@ def run(config):
|
||||||
# )
|
# )
|
||||||
# )
|
# )
|
||||||
|
|
||||||
|
# Remove chatbot if open, because will crash wizard div[4] (to div[5])
|
||||||
|
selenium_tool.check_bot_chat_and_close()
|
||||||
|
|
||||||
# Open View
|
# Open View
|
||||||
if config.open_me_devops:
|
if config.open_dashboard:
|
||||||
click(
|
selenium_tool.click(
|
||||||
driver,
|
"/html/body/header/nav/div/div[1]/div[2]/div/div/div/ul/li[3]/a",
|
||||||
"/html/body/header/nav/div/div[1]/div[2]/div/div/div/ul/li[2]/a",
|
|
||||||
)
|
)
|
||||||
click(
|
|
||||||
driver,
|
|
||||||
"/html/body/div[1]/main/div[2]/div/div/table/tbody/tr[1]/td[2]",
|
|
||||||
)
|
|
||||||
# Remove chat bot if open, because will crash wizard div[4] (to div[5])
|
|
||||||
check_bot_chat_and_close(driver)
|
|
||||||
if config.open_me_devops_auto:
|
|
||||||
click(
|
|
||||||
driver,
|
|
||||||
"/html/body/header/nav/div/div[1]/div[2]/div/div/div/ul/li[2]/a",
|
|
||||||
)
|
|
||||||
click(
|
|
||||||
driver,
|
|
||||||
"/html/body/div[1]/main/div[2]/div/div/table/tbody/tr[1]/td[2]",
|
|
||||||
)
|
|
||||||
|
|
||||||
# CG self
|
|
||||||
# Bouton modifier
|
|
||||||
# click(
|
|
||||||
# driver,
|
|
||||||
# "/html/body/div[1]/main/div[1]/div[2]/div/div/div[1]/button[1]",
|
|
||||||
# )
|
|
||||||
# Tab Code
|
|
||||||
# click(
|
|
||||||
# driver,
|
|
||||||
# "/html/body/div[1]/main/div[2]/div/div/div[7]/ul/li[2]/a",
|
|
||||||
# )
|
|
||||||
# Bouton Plan
|
|
||||||
click(
|
|
||||||
driver,
|
|
||||||
"/html/body/div[1]/main/div[2]/div/div/div[1]/div/button[1]",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Bouton autopoiesis
|
|
||||||
click(
|
|
||||||
driver,
|
|
||||||
"/html/body/div[4]/div/div/main/div/div/div[5]/table[2]/tbody/tr[2]/td[1]/button",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Bouton devops regenerate
|
|
||||||
click(
|
|
||||||
driver,
|
|
||||||
"/html/body/div[4]/div/div/main/div/div/div[6]/table[2]/tbody/tr[2]/td/button",
|
|
||||||
)
|
|
||||||
|
|
||||||
if config.open_me_devops_auto_force:
|
|
||||||
# Disable «Stop Execution if Env Not Clean
|
|
||||||
# click(
|
|
||||||
# driver,
|
|
||||||
# "/html/body/div[1]/main/div[2]/div/div/div[7]/div/div[2]/div[6]/table[2]/tbody/tr[3]/td[2]/div/label",
|
|
||||||
# )
|
|
||||||
# Option Force Generate
|
|
||||||
click(
|
|
||||||
driver,
|
|
||||||
"/html/body/div[4]/div/div/main/div/div/div[8]/table/tbody/tr[2]/td[1]/label",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Gen
|
|
||||||
# click(
|
|
||||||
# driver,
|
|
||||||
# "/html/body/div[1]/main/div[2]/div/div/div[7]/div/div[2]/div[2]/table[1]/tbody/tr/td[1]/button",
|
|
||||||
# )
|
|
||||||
|
|
||||||
# Next state
|
|
||||||
click(
|
|
||||||
driver,
|
|
||||||
"/html/body/div[4]/div/div/footer/div/footer/div/button[1]",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check error
|
|
||||||
# click(
|
|
||||||
# driver,
|
|
||||||
# "/html/body/div[1]/main/div[2]/div/div/div[7]/ul/li[13]/a",
|
|
||||||
# )
|
|
||||||
# click(driver, "/html/body/div[1]/main/div[2]/div/div/div[6]/div/div[14]/div/div[2]/table/tbody/tr[1]", time=60*2)
|
|
||||||
|
|
||||||
# Arrêter l'enregistrement
|
|
||||||
if config.record_mode:
|
|
||||||
video_recorder.stop()
|
|
||||||
|
|
||||||
# Open conversation chat
|
# Open conversation chat
|
||||||
# conversation_button = driver.find_element(By.XPATH, '/html/body/header/nav/ul[3]/li[2]/a')
|
# conversation_button = selenium_tool.driver.find_element(By.XPATH, '/html/body/header/nav/ul[3]/li[2]/a')
|
||||||
# conversation_button.click()
|
# conversation_button.click()
|
||||||
|
|
||||||
# Close bot chat
|
# Close bot chat
|
||||||
# conversation_button = driver.find_element(By.XPATH, '/html/body/div[3]/div[1]/span[2]/a[2]')
|
# conversation_button = selenium_tool.driver.find_element(By.XPATH, '/html/body/div[3]/div[1]/span[2]/a[2]')
|
||||||
# conversation_button.click()
|
# conversation_button.click()
|
||||||
|
|
||||||
# Fermez le navigateur
|
# Fermez le navigateur
|
||||||
# driver.quit()
|
# selenium_tool.driver.quit()
|
||||||
|
|
||||||
|
|
||||||
|
def compute_args(args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
config = get_config()
|
parser = argparse.ArgumentParser(
|
||||||
run(config)
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
description="""Selenium script to open web browser to ERPLibre.""",
|
||||||
|
epilog="""\
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
# Generate parser
|
||||||
|
selenium_lib.fill_parser(parser)
|
||||||
|
fill_parser(parser)
|
||||||
|
args = parser.parse_args()
|
||||||
|
compute_args(args)
|
||||||
|
# Instance selenium tool
|
||||||
|
selenium_tool = selenium_lib.SeleniumLib(args)
|
||||||
|
selenium_tool.configure()
|
||||||
|
selenium_tool.start_record()
|
||||||
|
# Execute
|
||||||
|
run(args, selenium_tool)
|
||||||
|
selenium_tool.stop_record()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue