Merge branch 'dev' of github.com:bunkerity/bunkerweb into dev
This commit is contained in:
commit
1624c4e766
|
@ -8,14 +8,18 @@ RUN mkdir -p /usr/share/bunkerweb/deps && \
|
|||
cat /tmp/req/requirements.txt /tmp/req/requirements.txt.1 > /usr/share/bunkerweb/deps/requirements.txt && \
|
||||
rm -rf /tmp/req
|
||||
|
||||
# Install dependencies
|
||||
RUN apk add --no-cache --virtual .build-deps g++ gcc musl-dev jpeg-dev zlib-dev libffi-dev cairo-dev pango-dev gdk-pixbuf-dev openssl-dev cargo postgresql-dev && \
|
||||
# Install python dependencies
|
||||
RUN apk add --no-cache --virtual .build-deps g++ gcc musl-dev jpeg-dev zlib-dev libffi-dev cairo-dev pango-dev gdk-pixbuf-dev openssl-dev cargo postgresql-dev
|
||||
|
||||
# Install python requirements
|
||||
RUN export MAKEFLAGS="-j$(nproc)" && \
|
||||
pip install --no-cache-dir --upgrade pip && \
|
||||
pip install wheel && \
|
||||
pip install --no-cache-dir --upgrade wheel && \
|
||||
mkdir -p /usr/share/bunkerweb/deps/python && \
|
||||
export MAKEFLAGS="-j$(nproc)" && \
|
||||
pip install --no-cache-dir --require-hashes --target /usr/share/bunkerweb/deps/python -r /usr/share/bunkerweb/deps/requirements.txt && \
|
||||
apk del .build-deps
|
||||
pip install --no-cache-dir --require-hashes --target /usr/share/bunkerweb/deps/python -r /usr/share/bunkerweb/deps/requirements.txt
|
||||
|
||||
# Remove build dependencies
|
||||
RUN apk del .build-deps
|
||||
|
||||
# Copy files
|
||||
# can't exclude specific files/dir from . so we are copying everything by hand
|
||||
|
|
|
@ -725,7 +725,7 @@ class Database:
|
|||
config[setting.id] = (
|
||||
default
|
||||
if methods is False
|
||||
else {"value": default, "method": "default"}
|
||||
else {"value": default, "global": True, "method": "default"}
|
||||
)
|
||||
|
||||
global_values = (
|
||||
|
@ -750,6 +750,7 @@ class Database:
|
|||
if methods is False
|
||||
else {
|
||||
"value": global_value.value,
|
||||
"global": True,
|
||||
"method": global_value.method,
|
||||
}
|
||||
)
|
||||
|
@ -798,13 +799,16 @@ class Database:
|
|||
if methods is False
|
||||
else {
|
||||
"value": service_setting.value,
|
||||
"global": False,
|
||||
"method": service_setting.method,
|
||||
}
|
||||
)
|
||||
|
||||
servers = " ".join(service.id for service in session.query(Services).all())
|
||||
config["SERVER_NAME"] = (
|
||||
servers if methods is False else {"value": servers, "method": "default"}
|
||||
servers
|
||||
if methods is False
|
||||
else {"value": servers, "global": True, "method": "default"}
|
||||
)
|
||||
|
||||
return config
|
||||
|
@ -852,7 +856,7 @@ class Database:
|
|||
tmp_config.pop(key)
|
||||
else:
|
||||
tmp_config[key] = (
|
||||
{"value": value["value"], "method": "default"}
|
||||
{"value": value["value"], "global": True, "method": "default"}
|
||||
if methods is True
|
||||
else value
|
||||
)
|
||||
|
|
|
@ -9,15 +9,18 @@ RUN mkdir -p /usr/share/bunkerweb/deps && \
|
|||
cat /tmp/req/requirements.txt /tmp/req/requirements.txt.1 /tmp/req/requirements.txt.2 > /usr/share/bunkerweb/deps/requirements.txt && \
|
||||
rm -rf /tmp/req
|
||||
|
||||
# Install python dependencies
|
||||
RUN apk add --no-cache --virtual .build-deps g++ gcc musl-dev jpeg-dev zlib-dev libffi-dev cairo-dev pango-dev gdk-pixbuf-dev openssl-dev cargo postgresql-dev
|
||||
|
||||
# Install python requirements
|
||||
RUN apk add --no-cache --virtual .build-deps g++ gcc musl-dev jpeg-dev zlib-dev libffi-dev cairo-dev pango-dev gdk-pixbuf-dev openssl-dev cargo postgresql-dev && \
|
||||
RUN export MAKEFLAGS="-j$(nproc)" && \
|
||||
pip install --no-cache-dir --upgrade pip && \
|
||||
pip install wheel && \
|
||||
pip install --no-cache-dir --upgrade wheel && \
|
||||
mkdir -p /usr/share/bunkerweb/deps/python && \
|
||||
export MAKEFLAGS="-j$(nproc)" && \
|
||||
pip install --no-cache-dir --require-hashes --target /usr/share/bunkerweb/deps/python -r /usr/share/bunkerweb/deps/requirements.txt && \
|
||||
pip install --no-cache-dir gunicorn && \
|
||||
apk del .build-deps
|
||||
pip install --no-cache-dir --require-hashes --target /usr/share/bunkerweb/deps/python -r /usr/share/bunkerweb/deps/requirements.txt
|
||||
|
||||
# Remove build dependencies
|
||||
RUN apk del .build-deps
|
||||
|
||||
# Copy files
|
||||
# can't exclude specific files/dir from . so we are copying everything by hand
|
||||
|
|
|
@ -9,14 +9,18 @@ RUN mkdir -p /usr/share/bunkerweb/deps && \
|
|||
cat /tmp/req/requirements.txt /tmp/req/requirements.txt.1 /tmp/req/requirements.txt.2 > /usr/share/bunkerweb/deps/requirements.txt && \
|
||||
rm -rf /tmp/req
|
||||
|
||||
# Install python dependencies
|
||||
RUN apk add --no-cache --virtual .build-deps g++ gcc musl-dev jpeg-dev zlib-dev libffi-dev cairo-dev pango-dev gdk-pixbuf-dev openssl-dev cargo postgresql-dev
|
||||
|
||||
# Install python requirements
|
||||
RUN apk add --no-cache --virtual .build-deps g++ gcc musl-dev jpeg-dev zlib-dev libffi-dev cairo-dev pango-dev gdk-pixbuf-dev openssl-dev cargo file make postgresql-dev && \
|
||||
RUN export MAKEFLAGS="-j$(nproc)" && \
|
||||
pip install --no-cache-dir --upgrade pip && \
|
||||
pip install wheel && \
|
||||
pip install --no-cache-dir --upgrade wheel && \
|
||||
mkdir -p /usr/share/bunkerweb/deps/python && \
|
||||
export MAKEFLAGS="-j$(nproc)" && \
|
||||
pip install --no-cache-dir --require-hashes --target /usr/share/bunkerweb/deps/python -r /usr/share/bunkerweb/deps/requirements.txt && \
|
||||
apk del .build-deps
|
||||
pip install --no-cache-dir --require-hashes --target /usr/share/bunkerweb/deps/python -r /usr/share/bunkerweb/deps/requirements.txt
|
||||
|
||||
# Remove build dependencies
|
||||
RUN apk del .build-deps
|
||||
|
||||
# Copy files
|
||||
# can't exclude specific files/dir from . so we are copying everything by hand
|
||||
|
|
|
@ -98,62 +98,19 @@ class Config:
|
|||
def get_plugins(
|
||||
self, *, external: bool = False, with_data: bool = False
|
||||
) -> List[dict]:
|
||||
plugins = []
|
||||
|
||||
for foldername in list(iglob("/etc/bunkerweb/plugins/*")) + (
|
||||
list(iglob("/usr/share/bunkerweb/core/*") if not external else [])
|
||||
):
|
||||
content = listdir(foldername)
|
||||
if "plugin.json" not in content:
|
||||
continue
|
||||
|
||||
with open(f"{foldername}/plugin.json", "r") as f:
|
||||
plugin = json_load(f)
|
||||
|
||||
plugin.update(
|
||||
{
|
||||
"page": False,
|
||||
"external": foldername.startswith("/etc/bunkerweb/plugins"),
|
||||
}
|
||||
)
|
||||
|
||||
plugin["method"] = "ui" if plugin["external"] else "manual"
|
||||
|
||||
if "ui" in content:
|
||||
if "template.html" in listdir(f"{foldername}/ui"):
|
||||
plugin["page"] = True
|
||||
|
||||
if with_data:
|
||||
plugin_content = BytesIO()
|
||||
with tar_open(
|
||||
fileobj=plugin_content, mode="w:gz", compresslevel=9
|
||||
) as tar:
|
||||
tar.add(foldername, arcname=basename(foldername), recursive=True)
|
||||
plugin_content.seek(0)
|
||||
value = plugin_content.getvalue()
|
||||
|
||||
plugin["data"] = value
|
||||
plugin["checksum"] = sha256(value).hexdigest()
|
||||
|
||||
plugins.append(plugin)
|
||||
plugins = self.__db.get_plugins(external=external, with_data=with_data)
|
||||
|
||||
plugins.sort(key=lambda x: x["name"])
|
||||
|
||||
with open("/usr/share/bunkerweb/settings.json", "r") as f:
|
||||
plugins.insert(
|
||||
0,
|
||||
{
|
||||
"id": "general",
|
||||
"name": "General",
|
||||
"description": "The general settings for the server",
|
||||
"version": "0.1",
|
||||
"stream": "partial",
|
||||
"external": False,
|
||||
"method": "manual",
|
||||
"page": False,
|
||||
"settings": json_load(f),
|
||||
},
|
||||
)
|
||||
general_plugin = None
|
||||
for plugin in plugins.copy():
|
||||
if plugin["id"] == "general":
|
||||
general_plugin = plugin
|
||||
plugins.remove(plugin)
|
||||
break
|
||||
|
||||
if general_plugin:
|
||||
plugins.insert(0, general_plugin)
|
||||
|
||||
return plugins
|
||||
|
||||
|
|
|
@ -169,33 +169,32 @@ class ServiceModal {
|
|||
}
|
||||
|
||||
//SET METHOD
|
||||
this.setDisabled(inp, defaultMethod);
|
||||
this.setDisabledDefault(inp, defaultMethod);
|
||||
});
|
||||
|
||||
const selects = this.modal.querySelectorAll("select");
|
||||
selects.forEach((select) => {
|
||||
const defaultMethod = "default";
|
||||
const defaultVal = select.getAttribute("data-default-value") || "";
|
||||
|
||||
document
|
||||
.querySelector(
|
||||
`[data-setting-select=${select.getAttribute(
|
||||
"data-setting-select-default"
|
||||
)}]`
|
||||
)
|
||||
.removeAttribute("disabled");
|
||||
|
||||
const defaultMethod = select.getAttribute("data-default-method");
|
||||
const defaultVal = select.getAttribute("data-default-value");
|
||||
//click the custom select dropdown to update select value
|
||||
select.parentElement
|
||||
.querySelector(
|
||||
`button[data-setting-select-dropdown-btn][value='${defaultVal}']`
|
||||
)
|
||||
.click();
|
||||
|
||||
this.setDisabled(select, defaultMethod);
|
||||
//set state to custom visible el
|
||||
const btnCustom = document.querySelector(
|
||||
`[data-setting-select=${select.getAttribute(
|
||||
"data-setting-select-default"
|
||||
)}]`
|
||||
);
|
||||
|
||||
this.setDisabledDefault(btnCustom, defaultMethod);
|
||||
});
|
||||
}
|
||||
|
||||
setDisabled(inp, method) {
|
||||
setDisabledDefault(inp, method) {
|
||||
if (method === "ui" || method === "default") {
|
||||
inp.removeAttribute("disabled");
|
||||
} else {
|
||||
|
@ -279,6 +278,7 @@ class ServiceModal {
|
|||
//change format to match id
|
||||
const value = data["value"];
|
||||
const method = data["method"];
|
||||
const global = data["global"];
|
||||
try {
|
||||
const inps = this.modal.querySelectorAll(`[name='${key}']`);
|
||||
|
||||
|
@ -326,12 +326,22 @@ class ServiceModal {
|
|||
}
|
||||
|
||||
//check disabled/enabled after setting values and methods
|
||||
this.setDisabled(inp, method);
|
||||
this.setDisabledServ(inp, method, global);
|
||||
});
|
||||
} catch (err) {}
|
||||
}
|
||||
}
|
||||
|
||||
setDisabledServ(inp, method, global) {
|
||||
if (global) return inp.removeAttribute("disabled");
|
||||
|
||||
if (method === "ui" || method === "default") {
|
||||
inp.removeAttribute("disabled");
|
||||
} else {
|
||||
inp.setAttribute("disabled", "");
|
||||
}
|
||||
}
|
||||
|
||||
//UTILS
|
||||
toggleModal() {
|
||||
this.modal.classList.toggle("hidden");
|
||||
|
@ -366,7 +376,7 @@ class Multiple {
|
|||
});
|
||||
|
||||
this.container.addEventListener("click", (e) => {
|
||||
//edit button
|
||||
//edit service button
|
||||
try {
|
||||
if (
|
||||
e.target.closest("button").getAttribute("data-services-action") ===
|
||||
|
@ -387,7 +397,7 @@ class Multiple {
|
|||
this.setMultipleToDOM(sortMultiples);
|
||||
}
|
||||
} catch (err) {}
|
||||
//new button
|
||||
//new service button
|
||||
try {
|
||||
if (
|
||||
e.target.closest("button").getAttribute("data-services-action") ===
|
||||
|
@ -439,8 +449,10 @@ class Multiple {
|
|||
);
|
||||
//clone schema to create a group with new num
|
||||
const schemaClone = schema.cloneNode(true);
|
||||
//add special attribut for disabled logic
|
||||
this.changeCloneSuffix(schemaClone, setNum);
|
||||
this.setDisabled();
|
||||
//set disabled / enabled state
|
||||
this.setDisabledMultNew(schemaClone);
|
||||
this.showClone(schema, schemaClone);
|
||||
//insert new group before first one
|
||||
//show all groups
|
||||
|
@ -576,6 +588,7 @@ class Multiple {
|
|||
multiples[key] = {
|
||||
value: data["value"],
|
||||
method: data["method"],
|
||||
global: data["global"],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -609,16 +622,18 @@ class Multiple {
|
|||
const settingContainer = schemaCtnrClone.querySelector(
|
||||
`[data-setting-container="${name}"]`
|
||||
);
|
||||
//replace input info
|
||||
this.setSetting(data["value"], data["method"], settingContainer);
|
||||
//replace input info and disabled state
|
||||
this.setSetting(
|
||||
data["value"],
|
||||
data["method"],
|
||||
data["global"],
|
||||
settingContainer
|
||||
);
|
||||
}
|
||||
//send schema clone to DOM and show it
|
||||
this.showClone(schemaCtnr, schemaCtnrClone);
|
||||
}
|
||||
}
|
||||
|
||||
//disabled after update values and method
|
||||
this.setDisabled();
|
||||
}
|
||||
|
||||
changeCloneSuffix(schemaCtnrClone, suffix) {
|
||||
|
@ -677,7 +692,7 @@ class Multiple {
|
|||
});
|
||||
}
|
||||
|
||||
setSetting(value, method, settingContainer) {
|
||||
setSetting(value, method, global, settingContainer) {
|
||||
//update input
|
||||
try {
|
||||
const inps = settingContainer.querySelectorAll("input");
|
||||
|
@ -707,27 +722,29 @@ class Multiple {
|
|||
inp.value = value;
|
||||
inp.setAttribute("data-method", method);
|
||||
}
|
||||
this.setDisabledMultServ(inp, method, global);
|
||||
});
|
||||
} catch (err) {}
|
||||
//update select
|
||||
try {
|
||||
const select = settingContainer.querySelector("select");
|
||||
const name = select.getAttribute("data-services-setting-select-default");
|
||||
const selTxt = document.querySelector(
|
||||
`[data-services-setting-select-text='${name}']`
|
||||
select.setAttribute("data-method", method);
|
||||
|
||||
//click the custom select dropdown btn vavlue to update select value
|
||||
select.parentElement
|
||||
.querySelector(
|
||||
`button[data-setting-select-dropdown-btn][value='${defaultVal}']`
|
||||
)
|
||||
.click();
|
||||
|
||||
//set state to custom visible el
|
||||
const btnCustom = document.querySelector(
|
||||
`[data-setting-select=${select.getAttribute(
|
||||
"data-setting-select-default"
|
||||
)}]`
|
||||
);
|
||||
|
||||
selTxt.textContent = value;
|
||||
selTxt.setAttribute("value", value);
|
||||
const options = select.options;
|
||||
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
const option = options[i];
|
||||
option.value === value
|
||||
? option.setAttribute("selected")
|
||||
: option.removeAttribute("selected");
|
||||
}
|
||||
select.setAttribute("data-method", method);
|
||||
this.setDisabledMultServ(btnCustom, method, global);
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
|
@ -737,48 +754,55 @@ class Multiple {
|
|||
schemaCtnrClone.classList.add("grid");
|
||||
}
|
||||
|
||||
setDisabled() {
|
||||
const multipleCtnr = document.querySelectorAll(
|
||||
"[data-services-settings-multiple]"
|
||||
);
|
||||
multipleCtnr.forEach((container) => {
|
||||
const settings = container.querySelectorAll("[data-setting-container]");
|
||||
//global value isn't check at this point
|
||||
setDisabledMultNew(container) {
|
||||
const settings = container.querySelectorAll("[data-setting-container]");
|
||||
|
||||
settings.forEach((setting) => {
|
||||
//replace input info
|
||||
try {
|
||||
const inps = setting.querySelectorAll("input");
|
||||
inps.forEach((inp) => {
|
||||
const method = inp.getAttribute("data-method") || "default";
|
||||
if (method === "ui" || method === "default") {
|
||||
inp.removeAttribute("disabled");
|
||||
} else {
|
||||
inp.setAttribute("disabled", "");
|
||||
}
|
||||
});
|
||||
} catch (err) {}
|
||||
//or select
|
||||
try {
|
||||
const selects = setting.querySelectorAll("select");
|
||||
selects.forEach((select) => {
|
||||
const method = select.getAttribute("data-method") || "default";
|
||||
const name = select.getAttribute(
|
||||
"data-services-setting-select-default"
|
||||
);
|
||||
const selDOM = document.querySelector(
|
||||
`button[data-services-setting-select='${name}']`
|
||||
);
|
||||
if (method === "ui" || method === "default") {
|
||||
selDOM.removeAttribute("disabled", "");
|
||||
} else {
|
||||
selDOM.setAttribute("disabled", "");
|
||||
}
|
||||
});
|
||||
} catch (err) {}
|
||||
});
|
||||
settings.forEach((setting) => {
|
||||
//replace input info
|
||||
try {
|
||||
const inps = setting.querySelectorAll("input");
|
||||
inps.forEach((inp) => {
|
||||
const method = inp.getAttribute("data-default-method");
|
||||
if (method === "ui" || method === "default") {
|
||||
inp.removeAttribute("disabled");
|
||||
} else {
|
||||
inp.setAttribute("disabled", "");
|
||||
}
|
||||
});
|
||||
} catch (err) {}
|
||||
//or select
|
||||
try {
|
||||
const selects = setting.querySelectorAll("select");
|
||||
selects.forEach((select) => {
|
||||
const method = select.getAttribute("data-default-method");
|
||||
const name = select.getAttribute(
|
||||
"data-services-setting-select-default"
|
||||
);
|
||||
const selDOM = document.querySelector(
|
||||
`button[data-services-setting-select='${name}']`
|
||||
);
|
||||
if (method === "ui" || method === "default") {
|
||||
selDOM.removeAttribute("disabled", "");
|
||||
} else {
|
||||
selDOM.setAttribute("disabled", "");
|
||||
}
|
||||
});
|
||||
} catch (err) {}
|
||||
});
|
||||
}
|
||||
|
||||
//for already existing services multiples
|
||||
//global is check
|
||||
setDisabledMultServ(inp, method, global) {
|
||||
if (global) return inp.removeAttribute("disabled");
|
||||
|
||||
if (method === "ui" || method === "default") {
|
||||
inp.removeAttribute("disabled");
|
||||
} else {
|
||||
inp.setAttribute("disabled", "");
|
||||
}
|
||||
}
|
||||
//UTILS
|
||||
|
||||
getSuffixNumOrFalse(name) {
|
||||
|
|
|
@ -18,19 +18,18 @@ COPY requirements.txt .
|
|||
RUN pip install --no-cache -r requirements.txt
|
||||
|
||||
RUN touch test.txt && \
|
||||
zip -r test.zip test.txt && \
|
||||
zip test.zip test.txt && \
|
||||
rm test.txt
|
||||
|
||||
RUN echo '{ \
|
||||
"id": "discord", \
|
||||
"order": 999, \
|
||||
"name": "Discord", \
|
||||
"description": "Send alerts to a Discord channel (using webhooks).", \
|
||||
"version": "0.1", \
|
||||
"stream": "no", \
|
||||
"settings": {} \
|
||||
}' > plugin.json && \
|
||||
zip -r discord.zip plugin.json && \
|
||||
zip discord.zip plugin.json && \
|
||||
rm plugin.json
|
||||
|
||||
# Clean up
|
||||
|
|
276
tests/ui/main.py
276
tests/ui/main.py
|
@ -21,156 +21,155 @@ from selenium.common.exceptions import (
|
|||
TimeoutException,
|
||||
)
|
||||
|
||||
try:
|
||||
ready = False
|
||||
retries = 0
|
||||
while not ready:
|
||||
with suppress(RequestException):
|
||||
status_code = get("http://www.example.com/admin").status_code
|
||||
ready = False
|
||||
retries = 0
|
||||
while not ready:
|
||||
with suppress(RequestException):
|
||||
status_code = get("http://www.example.com/admin").status_code
|
||||
|
||||
if status_code > 500:
|
||||
print("An error occurred with the server, exiting ...", flush=True)
|
||||
exit(1)
|
||||
|
||||
ready = status_code < 400
|
||||
|
||||
if retries > 10:
|
||||
print("UI took too long to be ready, exiting ...", flush=True)
|
||||
exit(1)
|
||||
elif not ready:
|
||||
retries += 1
|
||||
print("Waiting for UI to be ready, retrying in 5s ...", flush=True)
|
||||
sleep(5)
|
||||
|
||||
print("UI is ready, starting tests ...", flush=True)
|
||||
|
||||
firefox_options = Options()
|
||||
if "geckodriver" not in listdir(Path.cwd()):
|
||||
firefox_options.add_argument("--headless")
|
||||
|
||||
print("Starting Firefox ...", flush=True)
|
||||
|
||||
def safe_get_element(
|
||||
driver, by: By, _id: str, *, multiple: bool = False, error: bool = False
|
||||
) -> Union[WebElement, List[WebElement]]:
|
||||
try:
|
||||
return WebDriverWait(driver, 4).until(
|
||||
EC.presence_of_element_located((by, _id))
|
||||
if not multiple
|
||||
else EC.presence_of_all_elements_located((by, _id))
|
||||
)
|
||||
except TimeoutException as e:
|
||||
if error:
|
||||
raise e
|
||||
|
||||
print(
|
||||
f'Element searched by {by}: "{_id}" not found, exiting ...', flush=True
|
||||
)
|
||||
if status_code > 500:
|
||||
print("An error occurred with the server, exiting ...", flush=True)
|
||||
exit(1)
|
||||
|
||||
def assert_button_click(driver, button: Union[str, WebElement]):
|
||||
clicked = False
|
||||
while not clicked:
|
||||
with suppress(ElementClickInterceptedException):
|
||||
if isinstance(button, str):
|
||||
button = safe_get_element(driver, By.XPATH, button)
|
||||
ready = status_code < 400
|
||||
|
||||
sleep(0.5)
|
||||
if retries > 10:
|
||||
print("UI took too long to be ready, exiting ...", flush=True)
|
||||
exit(1)
|
||||
elif not ready:
|
||||
retries += 1
|
||||
print("Waiting for UI to be ready, retrying in 5s ...", flush=True)
|
||||
sleep(5)
|
||||
|
||||
button.click()
|
||||
clicked = True
|
||||
print("UI is ready, starting tests ...", flush=True)
|
||||
|
||||
def assert_alert_message(driver, message: str):
|
||||
safe_get_element(driver, By.XPATH, "//button[@data-flash-sidebar-open='']")
|
||||
firefox_options = Options()
|
||||
if "geckodriver" not in listdir(Path.cwd()):
|
||||
firefox_options.add_argument("--headless")
|
||||
|
||||
sleep(0.3)
|
||||
print("Starting Firefox ...", flush=True)
|
||||
|
||||
assert_button_click(driver, "//button[@data-flash-sidebar-open='']")
|
||||
|
||||
error = False
|
||||
while True:
|
||||
try:
|
||||
alerts = safe_get_element(
|
||||
driver,
|
||||
By.XPATH,
|
||||
"//aside[@data-flash-sidebar='']/div[2]/div",
|
||||
multiple=True,
|
||||
error=True,
|
||||
)
|
||||
break
|
||||
except TimeoutException:
|
||||
if error:
|
||||
print("Messages list not found, exiting ...", flush=True)
|
||||
exit(1)
|
||||
error = True
|
||||
driver.refresh()
|
||||
|
||||
is_in = False
|
||||
for alert in alerts:
|
||||
if message in alert.text:
|
||||
is_in = True
|
||||
break
|
||||
|
||||
if not is_in:
|
||||
print(
|
||||
f'Message "{message}" not found in one of the messages in the list, exiting ...',
|
||||
flush=True,
|
||||
)
|
||||
exit(1)
|
||||
|
||||
print(
|
||||
f'Message "{message}" found in one of the messages in the list', flush=True
|
||||
def safe_get_element(
|
||||
driver, by: By, _id: str, *, multiple: bool = False, error: bool = False
|
||||
) -> Union[WebElement, List[WebElement]]:
|
||||
try:
|
||||
return WebDriverWait(driver, 4).until(
|
||||
EC.presence_of_element_located((by, _id))
|
||||
if not multiple
|
||||
else EC.presence_of_all_elements_located((by, _id))
|
||||
)
|
||||
except TimeoutException as e:
|
||||
if error:
|
||||
raise e
|
||||
|
||||
assert_button_click(
|
||||
driver, "//aside[@data-flash-sidebar='']/*[local-name() = 'svg']"
|
||||
)
|
||||
print(f'Element searched by {by}: "{_id}" not found, exiting ...', flush=True)
|
||||
exit(1)
|
||||
|
||||
def access_page(
|
||||
driver,
|
||||
driver_wait: WebDriverWait,
|
||||
button: Union[str, WebElement],
|
||||
name: str,
|
||||
message: bool = True,
|
||||
*,
|
||||
retries: int = 0,
|
||||
):
|
||||
assert_button_click(driver, button)
|
||||
|
||||
def assert_button_click(driver, button: Union[str, WebElement]):
|
||||
clicked = False
|
||||
while not clicked:
|
||||
with suppress(ElementClickInterceptedException):
|
||||
if isinstance(button, str):
|
||||
button = safe_get_element(driver, By.XPATH, button)
|
||||
|
||||
sleep(0.5)
|
||||
|
||||
button.click()
|
||||
clicked = True
|
||||
|
||||
|
||||
def assert_alert_message(driver, message: str):
|
||||
safe_get_element(driver, By.XPATH, "//button[@data-flash-sidebar-open='']")
|
||||
|
||||
sleep(0.3)
|
||||
|
||||
assert_button_click(driver, "//button[@data-flash-sidebar-open='']")
|
||||
|
||||
error = False
|
||||
while True:
|
||||
try:
|
||||
title = driver_wait.until(
|
||||
EC.presence_of_element_located(
|
||||
(By.XPATH, "/html/body/div/header/div/nav/h6")
|
||||
)
|
||||
alerts = safe_get_element(
|
||||
driver,
|
||||
By.XPATH,
|
||||
"//aside[@data-flash-sidebar='']/div[2]/div",
|
||||
multiple=True,
|
||||
error=True,
|
||||
)
|
||||
|
||||
if title.text != name.replace(" ", "_").title():
|
||||
print(f"Didn't get redirected to {name} page, exiting ...", flush=True)
|
||||
exit(1)
|
||||
break
|
||||
except TimeoutException:
|
||||
if retries < 3 and driver.current_url.split("/")[-1].startswith("/loading"):
|
||||
sleep(2)
|
||||
access_page(
|
||||
driver, driver_wait, button, name, message, retries=retries + 1
|
||||
)
|
||||
if error:
|
||||
print("Messages list not found, exiting ...", flush=True)
|
||||
exit(1)
|
||||
error = True
|
||||
driver.refresh()
|
||||
|
||||
print(f"{name.title()} page didn't load in time, exiting ...", flush=True)
|
||||
exit(1)
|
||||
is_in = False
|
||||
for alert in alerts:
|
||||
if message in alert.text:
|
||||
is_in = True
|
||||
break
|
||||
|
||||
if message:
|
||||
print(
|
||||
f"{name.title()} page loaded successfully",
|
||||
flush=True,
|
||||
if not is_in:
|
||||
print(
|
||||
f'Message "{message}" not found in one of the messages in the list, exiting ...',
|
||||
flush=True,
|
||||
)
|
||||
exit(1)
|
||||
|
||||
print(f'Message "{message}" found in one of the messages in the list', flush=True)
|
||||
|
||||
assert_button_click(
|
||||
driver, "//aside[@data-flash-sidebar='']/*[local-name() = 'svg']"
|
||||
)
|
||||
|
||||
|
||||
def access_page(
|
||||
driver,
|
||||
driver_wait: WebDriverWait,
|
||||
button: Union[str, WebElement],
|
||||
name: str,
|
||||
message: bool = True,
|
||||
*,
|
||||
retries: int = 0,
|
||||
):
|
||||
assert_button_click(driver, button)
|
||||
|
||||
try:
|
||||
title = driver_wait.until(
|
||||
EC.presence_of_element_located(
|
||||
(By.XPATH, "/html/body/div/header/div/nav/h6")
|
||||
)
|
||||
)
|
||||
|
||||
with webdriver.Firefox(
|
||||
service=Service(
|
||||
executable_path="./geckodriver"
|
||||
if "geckodriver" in listdir(Path.cwd())
|
||||
else "/usr/local/bin/geckodriver"
|
||||
),
|
||||
options=firefox_options,
|
||||
) as driver:
|
||||
if title.text != name.replace(" ", "_").title():
|
||||
print(f"Didn't get redirected to {name} page, exiting ...", flush=True)
|
||||
exit(1)
|
||||
except TimeoutException:
|
||||
if retries < 3 and driver.current_url.split("/")[-1].startswith("/loading"):
|
||||
sleep(2)
|
||||
access_page(driver, driver_wait, button, name, message, retries=retries + 1)
|
||||
|
||||
print(f"{name.title()} page didn't load in time, exiting ...", flush=True)
|
||||
exit(1)
|
||||
|
||||
if message:
|
||||
print(
|
||||
f"{name.title()} page loaded successfully",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
|
||||
with webdriver.Firefox(
|
||||
service=Service(
|
||||
executable_path="./geckodriver"
|
||||
if "geckodriver" in listdir(Path.cwd())
|
||||
else "/usr/local/bin/geckodriver"
|
||||
),
|
||||
options=firefox_options,
|
||||
) as driver:
|
||||
try:
|
||||
driver.delete_all_cookies()
|
||||
driver.maximize_window()
|
||||
driver_wait = WebDriverWait(driver, 30)
|
||||
|
@ -901,6 +900,8 @@ try:
|
|||
driver, By.XPATH, "//input[@type='file' and @name='file']"
|
||||
).send_keys(join(Path.cwd(), "test.zip"))
|
||||
|
||||
sleep(2)
|
||||
|
||||
access_page(
|
||||
driver,
|
||||
driver_wait,
|
||||
|
@ -909,8 +910,6 @@ try:
|
|||
False,
|
||||
)
|
||||
|
||||
sleep(2)
|
||||
|
||||
print(
|
||||
"The bad plugin has been rejected, trying to add a good plugin ...",
|
||||
flush=True,
|
||||
|
@ -920,6 +919,8 @@ try:
|
|||
driver, By.XPATH, "//input[@type='file' and @name='file']"
|
||||
).send_keys(join(Path.cwd(), "discord.zip"))
|
||||
|
||||
sleep(2)
|
||||
|
||||
access_page(
|
||||
driver,
|
||||
driver_wait,
|
||||
|
@ -1300,10 +1301,11 @@ try:
|
|||
exit(1)
|
||||
|
||||
print("Successfully logged out, tests are done", flush=True)
|
||||
except SystemExit:
|
||||
exit(1)
|
||||
except:
|
||||
print(f"Something went wrong, exiting ...\n{format_exc()}", flush=True)
|
||||
exit(1)
|
||||
except SystemExit:
|
||||
exit(1)
|
||||
except:
|
||||
print(f"Something went wrong, exiting ...\n{format_exc()}", flush=True)
|
||||
driver.save_screenshot("error.png")
|
||||
exit(1)
|
||||
|
||||
exit(0)
|
||||
|
|
Loading…
Reference in New Issue