Page MenuHomePhorge

D310.1781478303.diff
No OneTemporary

Size
18 KB
Referenced Files
None
Subscribers
None

D310.1781478303.diff

diff --git a/.gitignore b/.gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,6 @@
packaging/GNU-Linux/flatpak/venv
packaging/GNU-Linux/flatpak/flatpak-build
packaging/GNU-Linux/flatpak/vodozemac-bindings
+*.pyc
+*.pyo
+__pycache__
diff --git a/CMakeLists.txt b/CMakeLists.txt
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -25,6 +25,7 @@
set(KF_MIN_VERSION 6.0.0)
option(kazv_LINK_BREEZE_ICONS "Link to Breeze icons library" OFF)
+option(kazv_ENABLE_APPIUM_TESTS "Run appium tests" "")
set(kazv_LINK_BREEZE_ICONS_MACRO 0)
if(kazv_LINK_BREEZE_ICONS)
set(kazv_LINK_BREEZE_ICONS_MACRO 1)
@@ -80,6 +81,16 @@
set(CMARK_TARGET_NAME cmark)
endif()
+if(${kazv_ENABLE_APPIUM_TESTS} STREQUAL "")
+ find_package(SeleniumWebDriverATSPI)
+ if(SeleniumWebDriverATSPI_FOUND)
+ message(STATUS "Enabling appium tests because selenium-webdriver-at-spi is found. To turn it off, explicitly set kazv_ENABLE_APPIUM_TESTS to OFF")
+ set(kazv_ENABLE_APPIUM_TESTS ON)
+ endif()
+elseif(kazv_ENABLE_APPIUM_TESTS)
+ find_package(SeleniumWebDriverATSPI REQUIRED)
+endif()
+
kde_enable_exceptions()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -foperator-names -DQT_NO_EMIT")
diff --git a/packaging/GNU-Linux/appimage/build-unprivileged.sh b/packaging/GNU-Linux/appimage/build-unprivileged.sh
--- a/packaging/GNU-Linux/appimage/build-unprivileged.sh
+++ b/packaging/GNU-Linux/appimage/build-unprivileged.sh
@@ -2,9 +2,19 @@
export XDG_RUNTIME_DIR=/run/user/test
+git clone https://invent.kde.org/sdk/selenium-webdriver-at-spi.git
+pushd selenium-webdriver-at-spi
+virtualenv --system-site-packages venv
+source ./venv/bin/activate
+pip3 install -r requirements.txt
+CC=/usr/lib/ccache/gcc CXX=/usr/lib/ccache/g++ cmake -B build/
+cmake --build build/
+cmake --install build/
+popd
+
CC=/usr/lib/ccache/gcc CXX=/usr/lib/ccache/g++ \
cmake .. -DCMAKE_INSTALL_PREFIX="$KAZV_INSTALL_DIR" \
- -DCMAKE_PREFIX_PATH="$LIBKAZV_INSTALL_DIR;$DEPS_INSTALL_DIR" -DCMAKE_BUILD_TYPE=$BUILD_TYPE -Dkazv_KF_QT_MAJOR_VERSION=$KF_VER -Dkazv_LINK_BREEZE_ICONS=ON && \
+ -DCMAKE_PREFIX_PATH="$LIBKAZV_INSTALL_DIR;$DEPS_INSTALL_DIR" -DCMAKE_BUILD_TYPE=$BUILD_TYPE -Dkazv_KF_QT_MAJOR_VERSION=$KF_VER -Dkazv_LINK_BREEZE_ICONS=ON -Dkazv_ENABLE_APPIUM_TESTS=ON && \
make -j$JOBS && \
make -j$JOBS DESTDIR=AppDir install && \
dbus-launch --exit-with-session -- bash "$thisDir"/test.sh
diff --git a/packaging/GNU-Linux/appimage/build.sh b/packaging/GNU-Linux/appimage/build.sh
--- a/packaging/GNU-Linux/appimage/build.sh
+++ b/packaging/GNU-Linux/appimage/build.sh
@@ -56,6 +56,18 @@
libqt6sql6-sqlite
libqcoro6core0t64
qcoro-qt6-dev
+ # https://develop.kde.org/docs/apps/tests/appium/#building-manually
+ libkf6windowsystem-dev
+ libwayland-dev
+ libkpipewire-dev
+ kwin-dev
+ kwayland-dev
+ plasma-wayland-protocols
+ python3-pip
+ python3-virtualenv
+ ruby
+ accerciser
+ orca
)
export QMAKE=qmake6
cp -v packaging/GNU-Linux/appimage/kde-neon-noble.list /etc/apt/sources.list.d/
@@ -72,8 +84,19 @@
export BUILD_TYPE=Debug
fi
+git clone https://invent.kde.org/sdk/selenium-webdriver-at-spi.git
+pushd selenium-webdriver-at-spi
+virtualenv --system-site-packages venv
+source ./venv/bin/activate
+pip3 install -r requirements.txt
+CC=/usr/lib/ccache/gcc CXX=/usr/lib/ccache/g++ cmake -B build/
+cmake --build build/
+cmake --install build/
+popd
+
useradd -m -U builder
mkdir build
+mkdir -pv ccache
chown -R builder:builder ccache
chown builder:builder build
cd build
diff --git a/packaging/GNU-Linux/appimage/test.sh b/packaging/GNU-Linux/appimage/test.sh
--- a/packaging/GNU-Linux/appimage/test.sh
+++ b/packaging/GNU-Linux/appimage/test.sh
@@ -12,7 +12,7 @@
ls /run/user/test
socket=/run/user/test/wayland-test
echo "Wayland socket is $socket"
-CTEST_OUTPUT_ON_FAILURE=ON QT_QPA_PLATFORM=wayland WAYLAND_DISPLAY="$socket" make test
+TEST_WITH_KWIN_WAYLAND=0 CTEST_OUTPUT_ON_FAILURE=ON QT_QPA_PLATFORM=wayland WAYLAND_DISPLAY="$socket" make test
ret=$?
diff --git a/src/contents/ui/StickerPicker.qml b/src/contents/ui/StickerPicker.qml
--- a/src/contents/ui/StickerPicker.qml
+++ b/src/contents/ui/StickerPicker.qml
@@ -38,7 +38,11 @@
icon.name: 'smiley'
text: packNameProvider.name
checked: stickerPicker.currentIndex === index
- onClicked: stickerPicker.currentIndex = index
+ onClicked: setCurrent()
+ Accessible.onPressAction: setCurrent()
+ function setCurrent() {
+ stickerPicker.currentIndex = index;
+ }
}
}
}
@@ -56,32 +60,41 @@
model: currentPack
cellWidth: stickerPicker.stickerSize + stickerPicker.stickerMargin
cellHeight: stickerPicker.stickerSize + stickerPicker.stickerMargin
- delegate: MouseArea {
+ focus: true
+ focusPolicy: Qt.StrongFocus
+ delegate: AbstractButton {
+ id: stickerButton
objectName: `sticker${index}`
property var sticker: currentPack.at(index)
height: stickerPicker.stickerSize + stickerPicker.stickerMargin
width: stickerPicker.stickerSize + stickerPicker.stickerMargin
+ topInset: 0
+ bottomInset: 0
+ leftInset: 0
+ rightInset: 0
+ padding: stickerPicker.stickerMargin
- Kazv.ImageAdapter {
- anchors.centerIn: parent
+ contentItem: Kazv.ImageAdapter {
fillMode: Image.PreserveAspectFit
height: stickerPicker.stickerSize
width: stickerPicker.stickerSize
mxcUri: sticker.mxcUri
}
- Rectangle {
- visible: hoverHandler.hovered
- z: -1
- anchors.fill: parent
+ hoverEnabled: true
+ background: Rectangle {
+ visible: stickerButton.hovered || stickerButton.visualFocus
color: Kirigami.Theme.activeBackgroundColor
}
- HoverHandler {
- id: hoverHandler
- }
+ Accessible.role: Accessible.Button
+ Accessible.name: sticker.body
+ Accessible.onPressAction: requestSendMessage()
+ onClicked: requestSendMessage()
- onClicked: stickerPicker.sendMessageRequested(sticker.makeEventJson())
+ function requestSendMessage() {
+ stickerPicker.sendMessageRequested(sticker.makeEventJson());
+ }
}
}
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -98,7 +98,6 @@
tst_SendMessageBoxDrafts.qml
tst_SendMessageBox.qml
tst_StickerPackNameProvider.qml
- tst_StickerPicker.qml
tst_TypingIndicator.qml
tst_UserNameProvider.qml
tst_UserPage.qml
@@ -107,4 +106,46 @@
message(STATUS "Adding quick test ${testfilename}")
string(REPLACE .qml "" testname ${testfilename})
add_test(NAME ${testname} COMMAND quicktest -input ${CMAKE_CURRENT_SOURCE_DIR}/quick-tests/${testfilename})
+ set_property(TEST ${testname}
+ PROPERTY LABELS "quicktest")
endforeach()
+
+if(kazv_ENABLE_APPIUM_TESTS)
+ qt_add_qml_module(kazvappiumcomponentqmlmodule
+ STATIC
+ URI moe.kazv.mxc.kazvappiumcomponent
+ VERSION 0.0
+ )
+
+ kazv_add_qml_files(kazvappiumcomponentqmlmodule
+ BASE_DIRECTORY run-appium-component-qml
+ SOURCES
+ Main.qml
+ )
+
+ add_executable(run-appium-component run-appium-component.cpp)
+ target_link_libraries(run-appium-component
+ PRIVATE
+ kazvappiumcomponentqmlmodule
+ kazvappiumcomponentqmlmoduleplugin
+ kazvtestlib
+ Qt::Quick
+ )
+
+ set(appium_tests_SOURCES
+ TestStickerPicker.py
+ )
+ foreach(testfilename ${appium_tests_SOURCES})
+ message(STATUS "Adding appium test ${testfilename}")
+ string(REPLACE .py "" testname ${testfilename})
+ add_test(NAME ${testname}
+ COMMAND
+ ${SELENIUM_WEBDRIVER_AT_SPI_RUN_PATH}
+ ${CMAKE_CURRENT_SOURCE_DIR}/appium-tests/${testfilename}
+ )
+ set_property(TEST ${testname}
+ PROPERTY ENVIRONMENT "KAZV_APPIUM_TEST_BIN=$<TARGET_FILE:run-appium-component>;TEST_WITH_VIDEO_RECORDER=0")
+ set_property(TEST ${testname}
+ PROPERTY LABELS "appiumtest")
+ endforeach()
+endif()
diff --git a/src/tests/appium-tests/README.md b/src/tests/appium-tests/README.md
new file mode 100644
--- /dev/null
+++ b/src/tests/appium-tests/README.md
@@ -0,0 +1,10 @@
+
+# Appium tests
+
+Currently, only component tests are defined.
+
+The component test relies on the loader entrypoint (`../run-appium-component.cpp` and `../run-appium-component-qml/Main.qml`).
+
+Each component test contains two parts, a `.py` file and a `.qml` file. The `.py` file has a class inherited from `utils.ComponentTest`, which loads the corresponding `.qml` file with the same base name through the loader entrypoint.
+
+To add a test, create `<TestName>.py` and `<TestName>.qml` under this directory, and then add the `<TestName>.py` file to `appium_tests_SOURCES` variable in the `CMakeLists.txt` file in the parent directory.
diff --git a/src/tests/appium-tests/TestStickerPicker.py b/src/tests/appium-tests/TestStickerPicker.py
new file mode 100755
--- /dev/null
+++ b/src/tests/appium-tests/TestStickerPicker.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+
+from helpers import utils
+from appium.webdriver.common.appiumby import AppiumBy
+from selenium.webdriver.support.ui import WebDriverWait
+import json
+
+class StickerPickerTest(utils.ComponentTest):
+ def test_sticker_picker(self):
+ sticker0 = self.driver.find_element(by=AppiumBy.NAME, value='some')
+ sticker0.click()
+ self.driver.find_element(by=AppiumBy.NAME, value='spy-count=1')
+ expected = {
+ 'type': 'm.sticker',
+ 'content': {
+ 'body': 'some',
+ 'url': 'mxc://example.org/some',
+ },
+ }
+ self.wait_for(lambda: self.get_json_for('spy-last-arg') == expected)
+
+ pack1 = self.driver.find_element(by=AppiumBy.CLASS_NAME, value='[page tab | Pack 1]')
+ pack1.click()
+ sticker2 = self.driver.find_element(by=AppiumBy.NAME, value='some2')
+ sticker2.click()
+ self.driver.find_element(by=AppiumBy.NAME, value='spy-count=2')
+ expected = {
+ 'type': 'm.sticker',
+ 'content': {
+ 'body': 'some2',
+ 'url': 'mxc://example.org/some2',
+ },
+ }
+ self.wait_for(lambda: self.get_json_for('spy-last-arg') == expected)
+
+if __name__ == '__main__':
+ utils.main(__file__)
diff --git a/src/tests/quick-tests/tst_StickerPicker.qml b/src/tests/appium-tests/TestStickerPicker.qml
rename from src/tests/quick-tests/tst_StickerPicker.qml
rename to src/tests/appium-tests/TestStickerPicker.qml
--- a/src/tests/quick-tests/tst_StickerPicker.qml
+++ b/src/tests/appium-tests/TestStickerPicker.qml
@@ -1,22 +1,18 @@
/*
* This file is part of kazv.
- * SPDX-FileCopyrightText: 2024 tusooa <tusooa@kazv.moe>
+ * SPDX-FileCopyrightText: 2026 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-import QtQuick 2.15
-import QtQuick.Layouts 1.15
-import QtTest 1.0
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtTest
import '../../contents/ui' as Kazv
-import 'test-helpers.js' as JsHelpers
-import 'test-helpers' as QmlHelpers
-
-import moe.kazv.mxc.kazv 0.0 as MK
-
-QmlHelpers.TestItem {
- id: item
+import 'qml-helpers' as AppiumQmlHelpers
+ColumnLayout {
function makeSticker(sticker) {
return {
type: 'm.sticker',
@@ -31,6 +27,7 @@
property list<ListModel> stickerPacks: [
ListModel {
id: stickerPack0
+ property string packName: 'Pack 0'
ListElement {
shortCode: 'some'
body: 'some'
@@ -51,6 +48,7 @@
},
ListModel {
id: stickerPack1
+ property string packName: 'Pack 1'
ListElement {
shortCode: 'some2'
body: 'some2'
@@ -76,7 +74,35 @@
signalName: 'sendMessageRequested'
}
+ function reset() {
+ sendMessageRequestedSpy.clear();
+ sendMessageRequestedSpy.target = stickerPicker;
+ spyLastArgLabel.value = spyLastArgLabel.getValue('');
+ }
+
+ AppiumQmlHelpers.InfoLabel {
+ tag: 'spy-count'
+ value: sendMessageRequestedSpy.count
+ }
+
+ AppiumQmlHelpers.InfoLabel {
+ id: spyLastArgLabel
+ Connections {
+ target: stickerPicker
+ function onSendMessageRequested(json) {
+ spyLastArgLabel.value = spyLastArgLabel.getValue(json);
+ }
+ }
+ tag: 'spy-last-arg'
+ value: getValue('')
+
+ function getValue(json) {
+ return JSON.stringify(json);
+ }
+ }
+
Kazv.StickerPicker {
+ Layout.fillHeight: true
id: stickerPicker
stickerPackList: ListModel {
id: stickerPackListModel
@@ -89,32 +115,4 @@
}
}
}
-
- TestCase {
- id: stickerPickerTest
- name: 'StickerPickerTest'
- when: windowShown
-
- function init() {
- sendMessageRequestedSpy.clear();
- sendMessageRequestedSpy.target = stickerPicker;
- }
-
- function test_stickerPicker() {
- verify(findChild(stickerPicker, 'stickerPack0'));
- verify(findChild(stickerPicker, 'stickerPack1'));
- verify(findChild(stickerPicker, 'sticker0'));
- verify(findChild(stickerPicker, 'sticker1'));
- const stickerButton = findChild(stickerPicker, 'sticker1');
- mouseClick(stickerButton);
- tryVerify(() => sendMessageRequestedSpy.count === 1, 1000);
- verify(JsHelpers.deepEqual(sendMessageRequestedSpy.signalArguments[0][0], makeSticker(stickerPack0.get(1))));
-
- mouseClick(findChild(stickerPicker, 'stickerPack1'));
- tryVerify(() => findChild(stickerPicker, 'sticker0').sticker.mxcUri === 'mxc://example.org/some2');
- mouseClick(findChild(stickerPicker, 'sticker0'));
- tryVerify(() => sendMessageRequestedSpy.count === 2, 1000);
- verify(JsHelpers.deepEqual(sendMessageRequestedSpy.signalArguments[1][0], makeSticker(stickerPack1.get(0))));
- }
- }
}
diff --git a/src/tests/appium-tests/helpers/__init__.py b/src/tests/appium-tests/helpers/__init__.py
new file mode 100644
diff --git a/src/tests/appium-tests/helpers/utils.py b/src/tests/appium-tests/helpers/utils.py
new file mode 100644
--- /dev/null
+++ b/src/tests/appium-tests/helpers/utils.py
@@ -0,0 +1,62 @@
+# This file is part of kazv.
+# SPDX-FileCopyrightText: 2026 tusooa <tusooa@kazv.moe>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+import unittest
+from appium import webdriver
+from appium.options.common.base import AppiumOptions
+from appium.webdriver.common.appiumby import AppiumBy
+import sys
+import os
+import json
+import time
+
+tests_dir = None
+program_name = None
+module_name = None
+
+class TestUtilMixin:
+ def get_json_for(self, name):
+ elem = self.driver.find_element(by=AppiumBy.XPATH, value=f'//*[starts-with(@name, "{name}=")]')
+ print(elem, file=sys.stderr)
+ attr = elem.get_attribute('name')
+ print(attr, file=sys.stderr)
+ return json.loads(attr[len(name) + 1:])
+
+ def wait_for(self, pred, timeout_ms=10000, retry_ms=100):
+ steady_deadline = time.monotonic() + timeout_ms / 1000
+ satisfied = False
+ while time.monotonic() < steady_deadline:
+ if pred():
+ satisfied = True
+ break
+ time.sleep(retry_ms / 1000)
+ self.assertTrue(satisfied, msg=f'Predicate {pred} does not return truthy within {timeout_ms}ms')
+
+class ComponentTest(unittest.TestCase, TestUtilMixin):
+ @classmethod
+ def setUpClass(cls):
+ options = AppiumOptions()
+ print('----program name:', program_name, file=sys.stderr)
+ print('----tests dir:', tests_dir, file=sys.stderr)
+ print('----module name:', module_name, file=sys.stderr)
+ options.set_capability("app", f'env {program_name} {tests_dir}/{module_name}.qml')
+ cls.driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', options=options)
+ cls.driver.implicitly_wait = 10
+
+ def setUp(self):
+ self.driver.find_element(by=AppiumBy.NAME, value='appium-component-reset').click()
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.driver.quit()
+
+def main(filename):
+ global tests_dir, program_name, module_name
+ tests_dir = os.path.dirname(filename)
+ program_name = os.environ.get(
+ 'KAZV_APPIUM_TEST_BIN',
+ os.path.join(os.getcwd(), 'bin', 'run-appium-component')
+ )
+ module_name, _ext = os.path.splitext(os.path.basename(filename))
+ unittest.main()
diff --git a/src/tests/appium-tests/qml-helpers/InfoLabel.qml b/src/tests/appium-tests/qml-helpers/InfoLabel.qml
new file mode 100644
--- /dev/null
+++ b/src/tests/appium-tests/qml-helpers/InfoLabel.qml
@@ -0,0 +1,14 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2026 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import QtQuick
+import QtQuick.Controls
+
+Label {
+ property string tag
+ property string value
+ text: `${tag}=${value}`
+}
diff --git a/src/tests/run-appium-component-qml/Main.qml b/src/tests/run-appium-component-qml/Main.qml
new file mode 100644
--- /dev/null
+++ b/src/tests/run-appium-component-qml/Main.qml
@@ -0,0 +1,41 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2026 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+ApplicationWindow {
+ id: root
+ property string componentToLoad: ''
+ title: 'run-appium-component'
+ width: 1000
+ height: 800
+ visible: true
+
+ header: ToolBar {
+ RowLayout {
+ Button {
+ text: 'appium-component-reset'
+ onClicked: root.reset()
+ }
+ }
+ }
+
+ Loader {
+ id: loader
+ source: root.componentToLoad
+ anchors.fill: parent
+ }
+
+ function reset() {
+ if (loader.item && loader.item.reset) {
+ loader.item.reset();
+ } else {
+ console.log('Reset is not defined by loader item');
+ }
+ }
+}
diff --git a/src/tests/run-appium-component.cpp b/src/tests/run-appium-component.cpp
new file mode 100644
--- /dev/null
+++ b/src/tests/run-appium-component.cpp
@@ -0,0 +1,29 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2026 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <QQmlApplicationEngine>
+#include <QGuiApplication>
+#include <QFileInfo>
+
+using namespace Qt::Literals::StringLiterals;
+
+Q_DECL_EXPORT int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+ QQmlApplicationEngine engine;
+ engine.loadFromModule(u"moe.kazv.mxc.kazvappiumcomponent"_s, u"Main"_s);
+
+ if (engine.rootObjects().isEmpty()) {
+ return -1;
+ }
+
+ auto window = engine.rootObjects().at(0);
+ qDebug() << "Loading component" << argv[1];
+ auto fileInfo = QFileInfo(QString::fromLocal8Bit(argv[1]));
+ window->setProperty("componentToLoad", QUrl::fromLocalFile(fileInfo.absoluteFilePath()));
+
+ return app.exec();
+}

File Metadata

Mime Type
text/plain
Expires
Sun, Jun 14, 4:05 PM (16 h, 8 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1587900
Default Alt Text
D310.1781478303.diff (18 KB)

Event Timeline