artespy 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- artespy-0.1.0/.gitignore +10 -0
- artespy-0.1.0/.python-version +1 -0
- artespy-0.1.0/PKG-INFO +10 -0
- artespy-0.1.0/README.md +0 -0
- artespy-0.1.0/pyproject.toml +22 -0
- artespy-0.1.0/src/artespy/__init__.py +3 -0
- artespy-0.1.0/src/artespy/errors/__init__.py +22 -0
- artespy-0.1.0/src/artespy/selenium/handler.py +262 -0
- artespy-0.1.0/src/artespy/selenium/locators.py +96 -0
- artespy-0.1.0/src/artespy/selenium/pages/base.py +34 -0
- artespy-0.1.0/src/artespy/selenium/pages/components/base.py +8 -0
- artespy-0.1.0/src/artespy/selenium/pages/components/navbar.py +40 -0
- artespy-0.1.0/src/artespy/selenium/pages/home.py +28 -0
- artespy-0.1.0/src/artespy/selenium/pages/login.py +47 -0
- artespy-0.1.0/src/artespy/selenium/pages/mixins/table.py +105 -0
- artespy-0.1.0/src/artespy/selenium/pages/processo.py +378 -0
- artespy-0.1.0/uv.lock +499 -0
artespy-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
artespy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: artespy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author-email: Leonardo Hossokawa de Oliveira <estagiario.seguros04@artesp.sp.gov.br>
|
|
6
|
+
Requires-Python: >=3.13
|
|
7
|
+
Requires-Dist: lxml>=6.0.2
|
|
8
|
+
Requires-Dist: pandas>=2.3.3
|
|
9
|
+
Requires-Dist: selenium>=4.38.0
|
|
10
|
+
Requires-Dist: webdriver-manager>=4.0.2
|
artespy-0.1.0/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "artespy"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Leonardo Hossokawa de Oliveira", email = "estagiario.seguros04@artesp.sp.gov.br" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.13"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"lxml>=6.0.2",
|
|
12
|
+
"pandas>=2.3.3",
|
|
13
|
+
"selenium>=4.38.0",
|
|
14
|
+
"webdriver-manager>=4.0.2",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
artespy = "artespy:main"
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["hatchling"]
|
|
22
|
+
build-backend = "hatchling.build"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class HandlerException(Exception):
|
|
2
|
+
"""Classe base para exceptions relacionadas ao handler."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class UnknownAlertException(HandlerException):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ElementNotFoundException(HandlerException):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InvalidPageException(HandlerException):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SeiException(Exception):
|
|
18
|
+
"""Classe base para exceptions relacionadas ao SEI."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class InvalidCredentialsError(SeiException):
|
|
22
|
+
pass
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import datetime as dt
|
|
2
|
+
import os
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from selenium.webdriver import Chrome, ChromeOptions, ChromeService
|
|
7
|
+
from selenium.webdriver.support.ui import WebDriverWait
|
|
8
|
+
from webdriver_manager.chrome import ChromeDriverManager
|
|
9
|
+
|
|
10
|
+
from artespy.errors import HandlerException, InvalidPageException
|
|
11
|
+
from artespy.selenium.pages.home import HomePage
|
|
12
|
+
from artespy.selenium.pages.login import LoginPage
|
|
13
|
+
from artespy.selenium.pages.processo import ProcessoPage
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SeleniumHandler:
|
|
17
|
+
"""Handler Selenium para automatizar operações no portal SEI."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, headless: bool = False, timeout: int = 60) -> None:
|
|
20
|
+
self.headless: bool = headless
|
|
21
|
+
self.timeout: int = timeout
|
|
22
|
+
|
|
23
|
+
self.driver = None
|
|
24
|
+
self.wait = None
|
|
25
|
+
self._page = None
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def page(self):
|
|
29
|
+
if not self._page:
|
|
30
|
+
raise HandlerException("Página atual não inicializada.")
|
|
31
|
+
return self._page
|
|
32
|
+
|
|
33
|
+
def _inicializar_driver(self) -> None:
|
|
34
|
+
"""Instanciar um novo driver e espera explícita."""
|
|
35
|
+
opts = ChromeOptions()
|
|
36
|
+
opts.add_argument("--log-level=3")
|
|
37
|
+
opts.add_argument("--start-maximized")
|
|
38
|
+
if self.headless:
|
|
39
|
+
opts.add_argument("--headless")
|
|
40
|
+
opts.add_experimental_option("excludeSwitches", ["enable-automation"])
|
|
41
|
+
opts.add_experimental_option("useAutomationExtension", False)
|
|
42
|
+
opts.add_experimental_option(
|
|
43
|
+
"prefs",
|
|
44
|
+
{
|
|
45
|
+
"credentials_enable_service": False,
|
|
46
|
+
"profile.password_manager_enabled": False,
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
chrome_install = ChromeDriverManager().install()
|
|
51
|
+
folder = os.path.dirname(chrome_install)
|
|
52
|
+
driver_path = os.path.join(folder, "chromedriver.exe")
|
|
53
|
+
svc = ChromeService(driver_path)
|
|
54
|
+
|
|
55
|
+
self.driver = Chrome(service=svc, options=opts)
|
|
56
|
+
self.wait = WebDriverWait(self.driver, self.timeout)
|
|
57
|
+
|
|
58
|
+
def _encerrar_driver(self) -> None:
|
|
59
|
+
if self.driver:
|
|
60
|
+
self.driver.close()
|
|
61
|
+
self.driver = None
|
|
62
|
+
if self.wait:
|
|
63
|
+
self.wait = None
|
|
64
|
+
|
|
65
|
+
def __enter__(self) -> "SeleniumHandler":
|
|
66
|
+
"""Ponto de entrada do gerenciador de contexto."""
|
|
67
|
+
self._inicializar_driver()
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
71
|
+
"""Ponto de saída do gerenciador de contexto."""
|
|
72
|
+
self._encerrar_driver()
|
|
73
|
+
|
|
74
|
+
def _get_driver_e_espera(
|
|
75
|
+
self,
|
|
76
|
+
) -> tuple[Chrome, WebDriverWait[Chrome]]:
|
|
77
|
+
if not self.driver or not self.wait:
|
|
78
|
+
raise HandlerException("Driver e/ou espera não inicializados.")
|
|
79
|
+
return self.driver, self.wait
|
|
80
|
+
|
|
81
|
+
def acessar_sei(self, usuario: str, senha: str, orgao: str = "ARTESP"):
|
|
82
|
+
"""Acessa e faz login no portal SEI."""
|
|
83
|
+
driver, wait = self._get_driver_e_espera()
|
|
84
|
+
|
|
85
|
+
if not isinstance(usuario, str):
|
|
86
|
+
msg = "Acessando SEI: usuario deve ser uma string"
|
|
87
|
+
raise TypeError(msg) # pyright:ignore[reportUnreachable]
|
|
88
|
+
if not usuario:
|
|
89
|
+
msg = "Acessando SEI: usuário deve ser uma string não vazia"
|
|
90
|
+
raise ValueError(msg)
|
|
91
|
+
if not isinstance(senha, str):
|
|
92
|
+
msg = "Acessando SEI: senha deve ser uma string"
|
|
93
|
+
raise TypeError(msg) # pyright:ignore[reportUnreachable]
|
|
94
|
+
if not senha:
|
|
95
|
+
msg = "Acessando SEI: senha deve ser uma string não vazia"
|
|
96
|
+
raise ValueError(msg)
|
|
97
|
+
if not isinstance(orgao, str):
|
|
98
|
+
msg = "Acessando SEI: orgao deve ser uma string"
|
|
99
|
+
raise TypeError(msg) # pyright:ignore[reportUnreachable]
|
|
100
|
+
if not orgao:
|
|
101
|
+
msg = "Acessando SEI: órgão deve ser uma string não vazia"
|
|
102
|
+
raise ValueError(msg)
|
|
103
|
+
|
|
104
|
+
LoginPage(driver, wait).fazer_login(usuario, senha, orgao)
|
|
105
|
+
self._page = HomePage(driver, wait)
|
|
106
|
+
self._page.fechar_modal_novidades()
|
|
107
|
+
|
|
108
|
+
def trocar_unidade(self, unidade: str):
|
|
109
|
+
driver, wait = self._get_driver_e_espera()
|
|
110
|
+
|
|
111
|
+
if not isinstance(unidade, str):
|
|
112
|
+
msg = "Trocando unidade: unidade deve ser uma string"
|
|
113
|
+
raise TypeError(msg) # pyright:ignore[reportUnreachable]
|
|
114
|
+
if not unidade:
|
|
115
|
+
msg = "Trocando unidade: unidade deve ser uma string não vazia"
|
|
116
|
+
raise ValueError(msg)
|
|
117
|
+
|
|
118
|
+
self.page.navbar.trocar_unidade(unidade)
|
|
119
|
+
self._page = HomePage(driver, wait)
|
|
120
|
+
|
|
121
|
+
def buscar_processo(self, processo: str) -> bool:
|
|
122
|
+
driver, wait = self._get_driver_e_espera()
|
|
123
|
+
|
|
124
|
+
encontrado = self.page.navbar.buscar_processo(processo)
|
|
125
|
+
if encontrado:
|
|
126
|
+
self._page = ProcessoPage(driver, wait)
|
|
127
|
+
return encontrado
|
|
128
|
+
|
|
129
|
+
def ler_processos_recebidos(self) -> pd.DataFrame:
|
|
130
|
+
if not isinstance(self.page, HomePage):
|
|
131
|
+
msg = "página atual não é a página de controle de processos"
|
|
132
|
+
raise InvalidPageException(msg)
|
|
133
|
+
|
|
134
|
+
r = self.page.ler_tabela_recebidos()
|
|
135
|
+
if not r.empty:
|
|
136
|
+
r = (
|
|
137
|
+
r.drop(columns=["Unnamed: 0", "Recebidos", "Recebidos.2"])
|
|
138
|
+
.rename(columns={"Recebidos.1": "Processo"})
|
|
139
|
+
.drop_duplicates(ignore_index=True)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return r
|
|
143
|
+
|
|
144
|
+
def ler_processos_gerados(self) -> pd.DataFrame:
|
|
145
|
+
if not isinstance(self.page, HomePage):
|
|
146
|
+
msg = "página atual não é a página de controle de processos"
|
|
147
|
+
raise InvalidPageException(msg)
|
|
148
|
+
|
|
149
|
+
g = self.page.ler_tabela_gerados()
|
|
150
|
+
if not g.empty:
|
|
151
|
+
g = (
|
|
152
|
+
g.drop(columns=["Unnamed: 0", "Gerados", "Gerados.2"])
|
|
153
|
+
.rename(columns={"Gerados.1": "Processo"})
|
|
154
|
+
.drop_duplicates(ignore_index=True)
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
return g
|
|
158
|
+
|
|
159
|
+
def ler_processos_unidade(self) -> pd.DataFrame:
|
|
160
|
+
"""Retorna os processos recebidos e gerados em um DF."""
|
|
161
|
+
r = self.ler_processos_recebidos()
|
|
162
|
+
g = self.ler_processos_gerados()
|
|
163
|
+
return pd.concat([r, g], ignore_index=True)
|
|
164
|
+
|
|
165
|
+
def ler_status(self) -> str | list[str]:
|
|
166
|
+
if not isinstance(self.page, ProcessoPage):
|
|
167
|
+
msg = "página atual não é a página de processo"
|
|
168
|
+
raise InvalidPageException(msg)
|
|
169
|
+
return self.page.ler_status()
|
|
170
|
+
|
|
171
|
+
def ler_marcadores(self) -> list[str]:
|
|
172
|
+
if not isinstance(self.page, ProcessoPage):
|
|
173
|
+
msg = "página atual não é a página de processo"
|
|
174
|
+
raise InvalidPageException(msg)
|
|
175
|
+
return self.page.ler_marcadores()
|
|
176
|
+
|
|
177
|
+
def ler_prazo(self) -> dt.date | None:
|
|
178
|
+
if not isinstance(self.page, ProcessoPage):
|
|
179
|
+
msg = "página atual não é a página de processo"
|
|
180
|
+
raise InvalidPageException(msg)
|
|
181
|
+
return self.page.ler_prazo()
|
|
182
|
+
|
|
183
|
+
def ler_andamentos(self) -> pd.DataFrame:
|
|
184
|
+
if not isinstance(self.page, ProcessoPage):
|
|
185
|
+
msg = "página atual não é a página de processo"
|
|
186
|
+
raise InvalidPageException(msg)
|
|
187
|
+
return self.page.ler_andamentos()
|
|
188
|
+
|
|
189
|
+
def mover_documentos(self, nome: str, destino: str, motivo: str) -> None:
|
|
190
|
+
if not isinstance(self.page, ProcessoPage):
|
|
191
|
+
msg = "página atual não é a página de processo"
|
|
192
|
+
raise InvalidPageException(msg)
|
|
193
|
+
if not isinstance(nome, str):
|
|
194
|
+
msg = "nome deve ser uma string"
|
|
195
|
+
raise TypeError(msg) # pyright: ignore[reportUnreachable]
|
|
196
|
+
if not nome:
|
|
197
|
+
msg = "nome deve ser uma string não vazia"
|
|
198
|
+
raise ValueError(msg)
|
|
199
|
+
if not isinstance(destino, str):
|
|
200
|
+
msg = "destino deve ser uma string"
|
|
201
|
+
raise TypeError(msg) # pyright: ignore[reportUnreachable]
|
|
202
|
+
if not destino:
|
|
203
|
+
msg = "destino deve ser uma string não vazia"
|
|
204
|
+
raise ValueError(msg)
|
|
205
|
+
if not isinstance(motivo, str):
|
|
206
|
+
msg = "motivo deve ser uma string"
|
|
207
|
+
raise TypeError(msg) # pyright: ignore[reportUnreachable]
|
|
208
|
+
if not motivo:
|
|
209
|
+
msg = "motivo deve ser uma string não vazia"
|
|
210
|
+
raise ValueError(msg)
|
|
211
|
+
|
|
212
|
+
self.page.mover_documentos(nome, destino, motivo)
|
|
213
|
+
|
|
214
|
+
def concluir_processo(self) -> None:
|
|
215
|
+
if not isinstance(self.page, ProcessoPage):
|
|
216
|
+
msg = "página atual não é a página de processo"
|
|
217
|
+
raise InvalidPageException(msg)
|
|
218
|
+
self.page.concluir_processo()
|
|
219
|
+
|
|
220
|
+
def anexar_processo(self, processo: str) -> None:
|
|
221
|
+
if not isinstance(self.page, ProcessoPage):
|
|
222
|
+
msg = "página atual não é a página de processo"
|
|
223
|
+
raise InvalidPageException(msg)
|
|
224
|
+
self.page.anexar_processo(processo)
|
|
225
|
+
|
|
226
|
+
def conceder_vistas(
|
|
227
|
+
self,
|
|
228
|
+
email: str,
|
|
229
|
+
tipo: Literal["acompanhamento", "disponibilizacao"],
|
|
230
|
+
motivo: str,
|
|
231
|
+
dias: int,
|
|
232
|
+
token: str,
|
|
233
|
+
) -> None:
|
|
234
|
+
if not isinstance(self.page, ProcessoPage):
|
|
235
|
+
msg = "página atual não é a página de processo"
|
|
236
|
+
raise InvalidPageException(msg)
|
|
237
|
+
if not isinstance(email, str):
|
|
238
|
+
msg = "email deve ser uma string"
|
|
239
|
+
raise TypeError(msg) # pyright: ignore[reportUnreachable]
|
|
240
|
+
if not email:
|
|
241
|
+
msg = "email deve ser uma string não vazia"
|
|
242
|
+
raise ValueError(msg)
|
|
243
|
+
if not isinstance(motivo, str):
|
|
244
|
+
msg = "motivo deve ser uma string"
|
|
245
|
+
raise TypeError(msg) # pyright: ignore[reportUnreachable]
|
|
246
|
+
if not motivo:
|
|
247
|
+
msg = "motivo deve ser uma string não vazia"
|
|
248
|
+
raise ValueError(msg)
|
|
249
|
+
if not isinstance(dias, int):
|
|
250
|
+
msg = "dias deve ser um int"
|
|
251
|
+
raise TypeError(msg) # pyright: ignore[reportUnreachable]
|
|
252
|
+
if not dias:
|
|
253
|
+
msg = "dias deve ser maior que zero"
|
|
254
|
+
raise ValueError(msg)
|
|
255
|
+
if not isinstance(token, str):
|
|
256
|
+
msg = "token deve ser uma string"
|
|
257
|
+
raise TypeError(msg) # pyright: ignore[reportUnreachable]
|
|
258
|
+
if not token:
|
|
259
|
+
msg = "token deve ser uma string não vazia"
|
|
260
|
+
raise ValueError(msg)
|
|
261
|
+
|
|
262
|
+
self.page.conceder_vistas(email, tipo, motivo, str(dias), token)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from typing import final
|
|
2
|
+
|
|
3
|
+
from selenium.webdriver.common.by import By
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@final
|
|
7
|
+
class Locators:
|
|
8
|
+
# Login
|
|
9
|
+
InputUsuario = By.ID, "txtUsuario"
|
|
10
|
+
InputSenha = By.ID, "pwdSenha"
|
|
11
|
+
SelOrgao = By.ID, "selOrgao"
|
|
12
|
+
BtnLogin = By.ID, "sbmAcessar"
|
|
13
|
+
|
|
14
|
+
# Geral
|
|
15
|
+
FecharModalNovidade = By.XPATH, "//*[@title='Fechar janela (ESC)']"
|
|
16
|
+
CxUnidade = By.CSS_SELECTOR, "#divInfraBarraSistemaPadrao #lnkInfraUnidade"
|
|
17
|
+
LinhaUnidade = By.XPATH, "//td[contains(text(), '{}')]"
|
|
18
|
+
BuscaRapida = By.ID, "txtPesquisaRapida"
|
|
19
|
+
ProcessoNaoEncontrado = By.CSS_SELECTOR, "#conteudo > div"
|
|
20
|
+
|
|
21
|
+
# Controle de processos
|
|
22
|
+
TblRecebidos = By.ID, "tblProcessosRecebidos"
|
|
23
|
+
SelPagRecebidos = By.ID, "selRecebidosPaginacaoSuperior"
|
|
24
|
+
ProxPagRecebidos = By.ID, "lnkRecebidosProximaPaginaSuperior"
|
|
25
|
+
TblGerados = By.ID, "tblProcessosGerados"
|
|
26
|
+
SelPagGerados = By.ID, "selGeradosPaginacaoSuperior"
|
|
27
|
+
ProxPagGerados = By.ID, "lnkGeradosProximaPaginaSuperior"
|
|
28
|
+
|
|
29
|
+
# Processo
|
|
30
|
+
IframeArvore = By.ID, "ifrArvore"
|
|
31
|
+
IframeConteudo = By.ID, "ifrConteudoVisualizacao"
|
|
32
|
+
IframeVisualizacao = By.ID, "ifrVisualizacao"
|
|
33
|
+
DivStatus = By.ID, "divArvoreInformacao"
|
|
34
|
+
UnidadesStatus = By.TAG_NAME, "a"
|
|
35
|
+
Marcadores = By.XPATH, "//*[contains(@title, 'Marcador')]"
|
|
36
|
+
Prazo = By.XPATH, '//*[contains(@title, "Controle de Prazo:")]'
|
|
37
|
+
BtnAndamentos = By.CSS_SELECTOR, "#divConsultarAndamento > a"
|
|
38
|
+
TipoHistorico = By.ID, "ancTipoHistorico"
|
|
39
|
+
TblAndamentos = By.ID, "tblHistorico"
|
|
40
|
+
SelPagAndamentos = By.ID, "selInfraPaginacaoSuperior"
|
|
41
|
+
ProxPagAndamentos = By.ID, "lnkInfraProximaPaginaSuperior"
|
|
42
|
+
DivArvore = By.ID, "divArvore"
|
|
43
|
+
Documentos = By.XPATH, "//*/a[@target='ifrConteudoVisualizacao']"
|
|
44
|
+
Documento = By.LINK_TEXT, "{}"
|
|
45
|
+
DivArvoreInfo = By.ID, "divArvoreInformacao"
|
|
46
|
+
# Ações
|
|
47
|
+
AcaoMover = By.XPATH, "//*[@title='Mover Documento para outro Processo']"
|
|
48
|
+
AcaoConcluir = By.XPATH, "//*[@title='Concluir Processo']"
|
|
49
|
+
AcaoAnexar = By.XPATH, "//*[@title='Anexar Processo']"
|
|
50
|
+
AcaoVistas = (
|
|
51
|
+
By.XPATH,
|
|
52
|
+
"//*[@title='Gerenciar Disponibilizações de Acesso Externo']",
|
|
53
|
+
)
|
|
54
|
+
AcaoMarcadores = By.XPATH, "//*[@title='Gerenciar Marcador']"
|
|
55
|
+
# Mover Documento
|
|
56
|
+
ProcessoDestino = By.ID, "txtProcedimentoDestino"
|
|
57
|
+
BtnPesquisar = By.ID, "btnPesquisar"
|
|
58
|
+
InputMotivoMover = By.ID, "txaMotivo"
|
|
59
|
+
BtnMover = By.ID, "sbmMover"
|
|
60
|
+
# Concluir Processo
|
|
61
|
+
BtnSalvarConclusao = By.ID, "sbmSalvar"
|
|
62
|
+
# Anexar Processo
|
|
63
|
+
InputAnexar = By.ID, "txtProtocolo"
|
|
64
|
+
BtnPesquisar = By.ID, "btnPesquisar"
|
|
65
|
+
BtnAnexar = By.ID, "sbmAnexar"
|
|
66
|
+
# Vistas
|
|
67
|
+
InputEmailUnidade = By.ID, "selEmailUnidade"
|
|
68
|
+
FiltroUsuarioExterno = By.ID, "lblUsuariosExternos"
|
|
69
|
+
InputDestinatario = By.ID, "txtDestinatario"
|
|
70
|
+
AutocompleteDestinatario = By.ID, "divInfraAjaxtxtDestinatario"
|
|
71
|
+
InputEmailDestinatario = By.ID, "txtEmailDestinatario"
|
|
72
|
+
InputMotivoVistas = By.ID, "txaMotivo"
|
|
73
|
+
OptVistasIntegral = By.ID, "lblIntegral"
|
|
74
|
+
OptVistasParcial = By.ID, "lblParcial"
|
|
75
|
+
PermitirInclusaoDocs = By.ID, "lblSinInclusao"
|
|
76
|
+
SelecionarDocs = By.ID, "imgLupaSeries"
|
|
77
|
+
SelecionarProtocolos = By.ID, "imgLupaProtocolos"
|
|
78
|
+
IframeModal = By.NAME, "modal-frame"
|
|
79
|
+
FecharModalDocs = By.XPATH, "//*[@title='Fechar janela (ESC)']"
|
|
80
|
+
SelPagDocs = By.ID, "selInfraPaginacaoSuperior"
|
|
81
|
+
BtnTransportar = (
|
|
82
|
+
By.CSS_SELECTOR,
|
|
83
|
+
"#divInfraBarraComandosSuperior > #btnTransportarSelecao",
|
|
84
|
+
)
|
|
85
|
+
OptDocAnexo = (
|
|
86
|
+
By.CSS_SELECTOR,
|
|
87
|
+
"a#lnkInfraID-1917 ~ div.infraCheckboxDiv > label",
|
|
88
|
+
)
|
|
89
|
+
OptDocManifestacaoTecnica = (
|
|
90
|
+
By.CSS_SELECTOR,
|
|
91
|
+
"a#lnkInfraID-64 ~ div.infraCheckboxDiv > label",
|
|
92
|
+
)
|
|
93
|
+
OptTodosDocs = By.ID, "imgInfraCheck"
|
|
94
|
+
InputDiasVistas = By.ID, "txtDias"
|
|
95
|
+
InputTokenVistas = By.ID, "pwdSenha"
|
|
96
|
+
BtnDisponibilizar = By.ID, "btnDisponibilizar"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from selenium.common.exceptions import TimeoutException
|
|
2
|
+
from selenium.webdriver import Chrome
|
|
3
|
+
from selenium.webdriver.support import expected_conditions as EC
|
|
4
|
+
from selenium.webdriver.support.ui import WebDriverWait
|
|
5
|
+
|
|
6
|
+
from artespy.selenium.pages.components.navbar import Navbar
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BasePage:
|
|
10
|
+
def __init__(self, driver: Chrome, wait: WebDriverWait[Chrome]) -> None:
|
|
11
|
+
self.driver: Chrome = driver
|
|
12
|
+
self.wait: WebDriverWait[Chrome] = wait
|
|
13
|
+
|
|
14
|
+
def get_texto_alerta(self, timeout=5) -> str:
|
|
15
|
+
try:
|
|
16
|
+
return (
|
|
17
|
+
WebDriverWait(self.driver, timeout)
|
|
18
|
+
.until(EC.alert_is_present())
|
|
19
|
+
.text
|
|
20
|
+
)
|
|
21
|
+
except TimeoutException:
|
|
22
|
+
return ""
|
|
23
|
+
|
|
24
|
+
def confirmar_alerta(self):
|
|
25
|
+
self.driver.switch_to.alert.accept()
|
|
26
|
+
|
|
27
|
+
def fechar_alerta(self):
|
|
28
|
+
self.driver.switch_to.alert.dismiss()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class BaseLoggedInPage(BasePage):
|
|
32
|
+
def __init__(self, driver: Chrome, wait: WebDriverWait[Chrome]) -> None:
|
|
33
|
+
super().__init__(driver, wait)
|
|
34
|
+
self.navbar: Navbar = Navbar(driver, wait)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from selenium.webdriver import Chrome
|
|
2
|
+
from selenium.webdriver.support.ui import WebDriverWait
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BaseComponent:
|
|
6
|
+
def __init__(self, driver: Chrome, wait: WebDriverWait[Chrome]) -> None:
|
|
7
|
+
self.driver: Chrome = driver
|
|
8
|
+
self.wait: WebDriverWait[Chrome] = wait
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from selenium.common.exceptions import TimeoutException
|
|
2
|
+
from selenium.webdriver import Keys
|
|
3
|
+
from selenium.webdriver.support import expected_conditions as EC
|
|
4
|
+
from selenium.webdriver.support.ui import WebDriverWait
|
|
5
|
+
|
|
6
|
+
from artespy.selenium.locators import Locators as lc
|
|
7
|
+
from artespy.selenium.pages.components.base import BaseComponent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Navbar(BaseComponent):
|
|
11
|
+
def get_unidade_atual(self) -> str:
|
|
12
|
+
return self.wait.until(EC.element_to_be_clickable(lc.CxUnidade)).text
|
|
13
|
+
|
|
14
|
+
def acessar_pag_unidade(self) -> None:
|
|
15
|
+
self.wait.until(EC.element_to_be_clickable(lc.CxUnidade)).click()
|
|
16
|
+
|
|
17
|
+
def trocar_unidade(self, unidade: str) -> None:
|
|
18
|
+
u = self.get_unidade_atual()
|
|
19
|
+
if u.lower() == unidade.strip().lower():
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
self.acessar_pag_unidade()
|
|
23
|
+
|
|
24
|
+
loc = lc.LinhaUnidade[0], lc.LinhaUnidade[1].format(unidade)
|
|
25
|
+
self.wait.until(EC.element_to_be_clickable(loc)).click()
|
|
26
|
+
|
|
27
|
+
def buscar_processo(self, processo: str) -> bool:
|
|
28
|
+
self.driver.switch_to.default_content()
|
|
29
|
+
busca = self.wait.until(EC.element_to_be_clickable(lc.BuscaRapida))
|
|
30
|
+
busca.send_keys(processo)
|
|
31
|
+
busca.send_keys(Keys.ENTER)
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
_ = WebDriverWait(self.driver, 5).until(
|
|
35
|
+
EC.visibility_of_element_located(lc.ProcessoNaoEncontrado)
|
|
36
|
+
)
|
|
37
|
+
except TimeoutException: # processo encontrado
|
|
38
|
+
return True
|
|
39
|
+
else:
|
|
40
|
+
return False
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from selenium.common.exceptions import TimeoutException
|
|
3
|
+
from selenium.webdriver.support import expected_conditions as EC
|
|
4
|
+
from selenium.webdriver.support.ui import WebDriverWait
|
|
5
|
+
|
|
6
|
+
from artespy.selenium.locators import Locators as lc
|
|
7
|
+
from artespy.selenium.pages.base import BaseLoggedInPage
|
|
8
|
+
from artespy.selenium.pages.mixins.table import TableMixin
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HomePage(BaseLoggedInPage, TableMixin):
|
|
12
|
+
def fechar_modal_novidades(self) -> None:
|
|
13
|
+
try:
|
|
14
|
+
WebDriverWait(self.driver, 5).until(
|
|
15
|
+
EC.element_to_be_clickable(lc.FecharModalNovidade)
|
|
16
|
+
).click()
|
|
17
|
+
except TimeoutException:
|
|
18
|
+
pass # modal ausente
|
|
19
|
+
|
|
20
|
+
def ler_tabela_recebidos(self) -> pd.DataFrame:
|
|
21
|
+
return self.ler_tabela(
|
|
22
|
+
lc.TblRecebidos, lc.SelPagRecebidos, lc.ProxPagRecebidos
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def ler_tabela_gerados(self) -> pd.DataFrame:
|
|
26
|
+
return self.ler_tabela(
|
|
27
|
+
lc.TblGerados, lc.SelPagGerados, lc.ProxPagGerados
|
|
28
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from selenium.webdriver.support import expected_conditions as EC
|
|
2
|
+
from selenium.webdriver.support.ui import Select
|
|
3
|
+
from artespy.errors import InvalidCredentialsError, UnknownAlertException
|
|
4
|
+
from artespy.selenium.locators import Locators as lc
|
|
5
|
+
from artespy.selenium.pages.base import BasePage
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LoginPage(BasePage):
|
|
9
|
+
URL: str = "https://sei.sp.gov.br/sip/login.php?sigla_orgao_sistema=GESP&sigla_sistema=SEI&infra_url=L3NlaS8="
|
|
10
|
+
|
|
11
|
+
def acessar(self) -> None:
|
|
12
|
+
self.driver.get(self.URL)
|
|
13
|
+
|
|
14
|
+
def preencher_usuario(self, usuario: str) -> None:
|
|
15
|
+
self.wait.until(EC.element_to_be_clickable(lc.InputUsuario)).send_keys(
|
|
16
|
+
usuario
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def preencher_senha(self, senha: str) -> None:
|
|
20
|
+
self.wait.until(EC.element_to_be_clickable(lc.InputSenha)).send_keys(
|
|
21
|
+
senha
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def selecionar_orgao(self, orgao: str) -> None:
|
|
25
|
+
Select(
|
|
26
|
+
self.wait.until(EC.element_to_be_clickable(lc.SelOrgao))
|
|
27
|
+
).select_by_visible_text(orgao)
|
|
28
|
+
|
|
29
|
+
def clicar_login(self) -> None:
|
|
30
|
+
self.wait.until(EC.element_to_be_clickable(lc.BtnLogin)).click()
|
|
31
|
+
|
|
32
|
+
def fazer_login(self, usuario: str, senha: str, orgao: str) -> None:
|
|
33
|
+
self.acessar()
|
|
34
|
+
|
|
35
|
+
self.preencher_usuario(usuario)
|
|
36
|
+
self.preencher_senha(senha)
|
|
37
|
+
self.selecionar_orgao(orgao)
|
|
38
|
+
self.clicar_login()
|
|
39
|
+
|
|
40
|
+
t = self.get_texto_alerta()
|
|
41
|
+
if t:
|
|
42
|
+
if t.lower() == "usuário ou senha inválida.":
|
|
43
|
+
msg = "usuário, senha ou órgão inválidos"
|
|
44
|
+
raise InvalidCredentialsError(msg)
|
|
45
|
+
else:
|
|
46
|
+
msg = f"alerta com mensagem inesperada: '{t}'"
|
|
47
|
+
raise UnknownAlertException(msg)
|