Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/(main)/player/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export default async function Page() {
<Player
episodes={episodeData}
hlsUrl="https://playertest.longtailvideo.com/adaptive/elephants_dream_v4/index.m3u8"
openingTime={14}
endTime={565}
/>
<div className={styles.article}>
<article>
Expand Down
2 changes: 2 additions & 0 deletions app/tests/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export default function TestPage() {
<Player
episodes={[]}
hlsUrl="https://playertest.longtailvideo.com/adaptive/elephants_dream_v4/index.m3u8"
openingTime={14}
endTime={565}
/>
);
}
3 changes: 3 additions & 0 deletions app/ui/custom-link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface customLinkProps
fontWeight?: number;
bgClass?: string;
hideTextOnMobile?: boolean;
onClick?: () => void;
}

/**
Expand Down Expand Up @@ -60,13 +61,15 @@ export default function CustomLink({
blurring = false,
fontWeight = 400,
hideTextOnMobile,
onClick,
...anhorProps
}: customLinkProps) {
if (ico !== undefined && (alt === undefined || icoSize === undefined))
throw Error("Вы указали изображение, но не указали его alt или icoSize");
return (
<Link
href={anhorProps.href || "/"}
onClick={onClick}
className={clsx(
"cursor-pointer flex justify-center items-center gap-2.5 w-fit duration-200 mobile:max-compact:p-3!",
`${typeof rounded === "boolean" ? rounded && "rounded-full" : rounded}`,
Expand Down
48 changes: 47 additions & 1 deletion app/ui/player/player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,60 @@ import {
} from "@vidstack/react";
import Image from "next/image";
import clsx from "clsx";
import { useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { EpisodesData } from "@/app/libs/types/other";
import styles from "./player.module.scss";
import PlayerSettings from "./desktop/player-settings";
import { Display } from "../fonts";
import MobPlayerSettings from "./mobile/player-settings";
import NextSeria from "./next-seria";
import CustomButton from "../custom-button";
import SeriaEl from "./seria-el";
import CustomLink from "../custom-link";

export interface PlayerProps {
hlsUrl: string;
episodes: EpisodesData[];
widthClass?: string;
customAspect?: number;
openingTime: number;
endTime: number;
}

export default function Player({
hlsUrl,
episodes,
widthClass = "w-full",
customAspect = 1.776898734177215,
openingTime = 30,
endTime = 100,
}: PlayerProps) {
const [started, setStarted] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [allSerias, setAllSeriasOpened] = useState(false);
const ref = useRef<MediaPlayerInstance>(null);
// Условия показывания многих элементов
const conditions = !started || allSerias;

useEffect(() => {
const player = ref.current;
if (!player) return undefined;

const updateTime = () => setCurrentTime(player.currentTime);
player.addEventListener("time-update", updateTime);

return () => {
player.removeEventListener("time-update", updateTime);
};
}, []);

const handleSkip = () => {
if (ref.current?.currentTime) {
ref.current.currentTime = openingTime;
}
return undefined;
};

return (
<MediaPlayer
playsInline
Expand Down Expand Up @@ -167,6 +194,25 @@ export default function Player({
styles.control,
)}
>
<CustomLink
text="Следующая серия"
hoverClass="hover:scale-101"
bgClass="bg-container-opacity"
classes={`ml-auto z-100 pointer-events-auto ${currentTime > endTime ? "block" : "hidden"}`}
onClick={() => {
ref.current?.pause();
}}
test-id="button: next_episode"
/>

<CustomButton
text="Пропустить опенинг"
hoverClass="hover:scale-101"
bgClass="bg-container-opacity"
classes={`ml-auto ${currentTime < 10 ? "block" : "hidden"}`}
onClick={handleSkip}
test-id="button: skip_opening"
/>
<TimeSlider.Root className={styles.timeSlider}>
<TimeSlider.Track className={styles.track}>
<TimeSlider.TrackFill className={styles.trackFill} />
Expand Down
29 changes: 28 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from pages.main_page import MainPage
from pages.sidebar_element import SideBar
import pytest
import allure
import os

def pytest_addoption(parser):
parser.addoption('--stage', action='store', default=None, help="Choose stage: local or dev")
Expand Down Expand Up @@ -53,7 +55,11 @@ def driver(request):
else:
"""Обработка ситуации, если не указан стейдж"""
raise pytest.UsageError("--stage should be local or dev")

yield driver
driver.delete_all_cookies()
driver.execute_script("window.localStorage.clear();")
driver.execute_script("window.sessionStorage.clear();")
driver.quit()

@pytest.fixture
Expand All @@ -68,4 +74,25 @@ def sidebar(driver):
def main_page(driver):
return MainPage(driver)



# делаем скриншот
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
rep = outcome.get_result()
if rep.when == 'call' and rep.failed:
mode = 'a' if os.path.exists('failures') else 'w'
try:
with open('failures', mode) as f:
if 'driver' in item.fixturenames:
web_driver = item.funcargs['driver']
else:
print('Fail to take screen-shot')
return
allure.attach(
web_driver.get_screenshot_as_png(),
name='screenshot',
attachment_type=allure.attachment_type.PNG
)
except Exception as e:
print('Fail to take screen-shot: {}'.format(e))
1 change: 1 addition & 0 deletions tests/locators/player_page_locators.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class PlayerLocators:
PLAY_VIDEOPLAYER = (By.XPATH, '//button[@class="player-module-scss-module__kFWiqG__start"]')
BOTTOM_PLAYER_CONTAINER = (By.XPATH, '//div[@class="player-module-scss-module__kFWiqG__control"]')
PLAYER_LOADER = (By.XPATH, '//div[@class="player-module-scss-module__kFWiqG__loader-container"]')
SKIP_OPENING_PLAYER = (By.CSS_SELECTOR, '[test-id="button: skip_opening"]')

MINI_PLAY_BUTTON = (By.XPATH, '//div[@class="player-module-scss-module__kFWiqG__side"]//button[@class="player-module-scss-module__kFWiqG__plays"]')
MINI_SKIP_BUTTON = (By.XPATH, '//div[@class="player-module-scss-module__kFWiqG__side"]//button[@class="player-module-scss-module__kFWiqG__skip"]')
Expand Down
90 changes: 84 additions & 6 deletions tests/pages/player_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from locators.player_page_locators import PlayerLocators
from locators.header_element_locators import HeaderElementLocators
import random
from time import sleep
from faker import Faker
from selenium.webdriver.common.action_chains import ActionChains
locators = PlayerLocators()
Expand Down Expand Up @@ -221,21 +222,99 @@ def click_to_play_player(self):
self.find(locators.PLAY_VIDEOPLAYER).click()
self.element_is_not_visible(locators.PLAY_VIDEOPLAYER, 100)
self.element_is_not_visible(locators.PLAYER_LOADER, 20)
# плеер всегд активный
self.player_always_is_active()
# ----------------------------
self.click_to_skip_opening()
#self.element_is_visible(locators.BOTTOM_PLAYER_CONTAINER, 20) УТОЧНИТЬ
assert self.find(locators.MINI_PLAY_BUTTON).get_attribute('aria-pressed') == "true", f'[Error] PlayerPage после нажатия на плеер не играет аниме. Стоит на паузе. Аттрибут aria_pressed = {self.find(locators.MINI_PLAY_BUTTON).get_attribute("aria-pressed")}'
#assert self.find(locators.MINI_PLAY_BUTTON).get_attribute('aria-pressed') == "true", f'[Error] PlayerPage после нажатия на плеер не играет аниме. Стоит на паузе. Аттрибут aria_pressed = {self.find(locators.MINI_PLAY_BUTTON).get_attribute("aria-pressed")}'

def player_always_is_active(self):
script = """
// Функция для поддержания элементов управления видимыми
function keepControlsVisible() {
// Находим основной элемент плеера
const player = document.querySelector('[data-media-player]');

if (!player) {
console.error('Плеер не найден');
return;
}

// Добавляем атрибуты, которые активируют элементы управления
player.setAttribute('data-focus', '');
player.setAttribute('data-controls', '');

// Дополнительно делаем элементы видимыми через CSS
const style = document.createElement('style');
style.innerHTML = `
[data-media-player][data-controls] .player-module-scss-module__kFWiqG__control,
[data-media-player][data-focus] .player-module-scss-module__kFWiqG__control {
opacity: 1 !important;
visibility: visible !important;
pointer-events: auto !important;
}

.player-module-scss-module__kFWiqG__darkness {
display: none !important;
}
`;
document.head.appendChild(style);

// Регулярная проверка (на случай, если плеер удаляет атрибуты)
const intervalId = setInterval(() => {
if (!player.hasAttribute('data-focus')) {
player.setAttribute('data-focus', '');
}
if (!player.hasAttribute('data-controls')) {
player.setAttribute('data-controls', '');
}
}, 500);

// Функция для остановки
window.stopKeepVisible = function() {
clearInterval(intervalId);
player.removeAttribute('data-focus');
player.removeAttribute('data-controls');
document.head.removeChild(style);
console.log('Скрипт остановлен. Элементы управления могут скрываться.');
};

console.log('Элементы управления теперь всегда видны!');
console.log('Для остановки введите: stopKeepVisible()');
}

// Запускаем скрипт
keepControlsVisible();

// Возвращаем true для подтверждения выполнения
return true;
"""
self.driver.execute_script(script)
self.element_is_visible(locators.CURRENT_TIME_PLAYER, 20)

def click_to_skip_opening(self):
sleep(1)
self.element_is_clickable(locators.SKIP_OPENING_PLAYER, 10)
self.find(locators.SKIP_OPENING_PLAYER).click()
self.element_is_not_visible(locators.SKIP_OPENING_PLAYER, 10)

def mouse_hover_to_player(self):
self.element_is_not_visible(locators.BOTTOM_PLAYER_CONTAINER, 20)
#self.element_is_not_visible(locators.BOTTOM_PLAYER_CONTAINER, 20)
player = self.find(locators.VIDEOPLAYER_CONTAINER)
actions = ActionChains(self.driver)
actions.move_to_element(player).perform()
self.element_is_visible(locators.BOTTOM_PLAYER_CONTAINER, 20)
#assert self.find(locators.MINI_PLAY_BUTTON).get_attribute('aria-pressed') == "true", f'[Error] PlayerPage после нажатия на плеер не играет аниме. Стоит на паузе. Аттрибут aria_pressed = {self.find(locators.MINI_PLAY_BUTTON).get_attribute("aria-pressed")}'

def activate_player_desktop(self):
player = self.find(locators.VIDEOPLAYER_CONTAINER)
actions = ActionChains(self.driver)
actions.move_to_element(player).perform()

def click_to_setting_buttons(self):
self.element_is_visible(locators.BOTTOM_PLAYER_CONTAINER, 20)
self.element_is_clickable(locators.SETTINGS_BUTTON_PLAYER, 20)
self.mouse_hover_to_player()
self.find(locators.SETTINGS_BUTTON_PLAYER).click()
self.element_is_visible(locators.SETTINGS_WINDOW_CONTAINER, 20)

Expand Down Expand Up @@ -264,14 +343,12 @@ def click_to_other_settings_button(self):
assert self.find(locators.SETTINGS_AFTER_CLICK_H1).text == "Другие настройки", f'[Error] PlayerPage После нажатия на кнопку "Качество" открылся не тот поп ап, либо у него неверный H1. Фактический результа = {self.find(locators.SETTINGS_LANGUAGE_H1).text}'

def click_to_subtitle_settings_button(self):
self.mouse_hover_to_player()
self.element_is_clickable(locators.SETTINGS_SUBTITLE_BUTTON, 20)
self.find(locators.SETTINGS_SUBTITLE_BUTTON).click()
self.element_is_visible(locators.SETTINGS_AFTER_CLICK_WINDOW, 20)
assert self.find(locators.SETTINGS_AFTER_CLICK_H1).text == "Субтитры", f'[Error] PlayerPage После нажатия на кнопку "Субтитры" открылся не тот поп ап, либо у него неверный H1. Фактический результа = {self.find(locators.SETTINGS_LANGUAGE_H1).text}'

def click_to_more_volume_settings_button(self):
self.mouse_hover_to_player()
self.element_is_clickable(locators.SETTINGS_MORE_VOLUME_BUTTON, 20)
self.find(locators.SETTINGS_MORE_VOLUME_BUTTON).click()
self.element_is_visible(locators.SETTINGS_AFTER_CLICK_WINDOW, 20)
Expand Down Expand Up @@ -310,9 +387,10 @@ def activate_mobile_player(self):
def click_to_play_player_mobile(self):
self.check_player()
self.find(locators.PLAY_VIDEOPLAYER).click()
self.activate_mobile_player()
self.element_is_not_visible(locators.PLAY_VIDEOPLAYER, 20)
self.element_is_clickable(locators.PLAY_BUTTON_MOBILE, 20)
self.player_always_is_active()
self.find(locators.SKIP_OPENING_PLAYER).click()
self.element_is_clickable(locators.PREV_EPISODE_BUTTON_MOBILE, 20)
self.element_is_clickable(locators.NEXT_EPISODE_BUTTON_MOBILE, 20)

Expand Down
4 changes: 3 additions & 1 deletion tests/test_player_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,9 @@ def test_quality_popup(driver):
playerpage = PlayerPage(driver)
playerpage.open('http://localhost:3001/player')
playerpage.click_to_play_player()
playerpage.mouse_hover_to_player()
playerpage.player_always_is_active()
sleep(30)
#playerpage.mouse_hover_to_player()
playerpage.click_to_setting_buttons()
playerpage.click_to_quality_button()

Expand Down