chore: merge qa automation repo as subfolder

Because as far as we know there's no good reason why
Volodimir insisted on it being a separate repository.

The merging of `desktop-qa-automation` was one using
[`git-filter-repo`](https://github.com/newren/git-filter-repo) tool and these instructions:
https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

Steps:
```sh
git clone git@github.com:status-im/status-desktop
git clone git@github.com:status-im/desktop-qa-automation
cd desktop-qa-automation
git filter-repo --to-subdirectory-filter test/e2e
cd ../status-desktop
git remote add test-e2e ../desktop-qa-automation
git fetch test-e2e --no-tags
git merge --no-edit --allow-unrelated-histories test-e2e/master
```

Here's proof that it can work for a nightly, but will definitely need more adjustments:
https://ci.status.im/job/status-desktop/job/systems/job/linux/job/x86_64/job/tests-e2e/3/

And here's and example PR E2E run:
https://ci.status.im/job/status-desktop/job/e2e/job/prs-merged/2/

Signed-off-by: Jakub Sokołowski <jakub@status.im>
This commit is contained in:
Jakub Sokołowski 2024-06-03 09:47:44 +02:00
commit 3f7484f0fc
No known key found for this signature in database
GPG Key ID: FE65CD384D5BF7B4
472 changed files with 17672 additions and 0 deletions

16
test/e2e/.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
configs/_local.py
.idea/
.venv/
*.pyc
tmp/
*.DS_Store
/local_run_results/
squish.ini
.envrc

52
test/e2e/README.md Normal file
View File

@ -0,0 +1,52 @@
# This repository manages UI tests for desktop application
## How to set up your environment
1. **MacOS**: https://www.notion.so/Mac-arch-x64-and-Intel-50ea48dae1d4481b882afdbfad38e95a
2. **Linux**: https://www.notion.so/Linux-21f7abd2bb684a0fb10057848760a889
3. **Windows**: https://www.notion.so/Windows-fbccd2b09b784b32ba4174233d83878d
**NOTE:** when MacOS and Linux are proven to be working, Windows guide could be outdated (no one yet set up Windows)
## Which build to use
1. you _can_ use your local dev build but sometimes tests hag there. To use it, just place a path to the executable to AUT_PATH in your _local.py config,
for example `AUT_PATH = "/Users/anastasiya/status-desktop/bin/nim_status_client"`
2. normally, please use CI build. Grab recent one from Jenkins job https://ci.status.im/job/status-desktop/job/nightly/
**2.1** Linux and Windows could be taken from nightly job
![img.png](img.png)
**2.2** Mac **requires entitlements** for Squish which we don't add by default, so please go here https://ci.status.im/job/status-desktop/job/systems/job/macos/
and select architecture you need (arm or intel), click Build with parameters and select Squish entitlements. Select a branch if u like (master is default)
![img_1.png](img_1.png)
## Pytest marks used
You can run tests by mark, just use it like this in command line:
```bash
python3 -m pytest -m critical
```
or directly in pycharm terminal:
```bash
pytest -m critical
```
You can obtain the list of all marks we have by running this `pytest --markers`
- `critical`, mark used to select the most important checks we do for PRs in desktop repository
(the same for our repo PRs)
- `xfail`, used to link tests to existing tickets in desktop, so if test fails it will be marked as
expected to fail in report with a reference to the ticket. At the same time, if such test is passing,
it will be shown as XPASS (unexpectedly passing) in report, which will indicate the initial bug is gone
- `skip`, used to just skip tests for various reasons, normally with a ticket linked
- `flaky`, used to mark the tests that are normally passing but sometimes fail. If such test passes, then
if will be shown as passed in report normally. If the test fails, then the total run wont be failed, but
the corresponding test will be marked as `xfail` in the report. It is done for a few tests that are not super
stable yet, but passes most of the time. This mark should be used with caution and in case of real need only.
- `timeout(timeout=180, method="thread")`, to catch excessively long test durations like deadlocked or hanging tests.
This is done by `pytest-timeout` plugin

251
test/e2e/ci/Jenkinsfile vendored Normal file
View File

@ -0,0 +1,251 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.8.6'
pipeline {
agent {
label "${params.AGENT} && x86_64 && qt-5.15.2"
}
parameters {
gitParameter(
name: 'GIT_REF',
description: 'Git branch to checkout.',
branchFilter: 'origin/(.*)',
branch: '',
defaultValue: 'master',
quickFilterEnabled: false,
selectedValue: 'DEFAULT',
sortMode: 'ASCENDING_SMART',
tagFilter: '*',
type: 'PT_BRANCH'
)
string(
name: 'BUILD_SOURCE',
description: 'URL to tar.gz file OR path to Jenkins build.',
defaultValue: getDefaultBuildSource()
)
string(
name: 'TEST_NAME',
description: 'Paste test name/part of test name to run specific test.',
defaultValue: ''
)
string(
name: 'TEST_SCOPE_FLAG',
description: 'Paste a known mark to run tests labeled with this mark',
defaultValue: getDefaultTestScopeFlag()
)
string(
name: 'TESTRAIL_RUN_NAME',
description: 'Test run name in Test Rail.',
defaultValue: ''
)
choice(
name: 'LOG_LEVEL',
description: 'Log level for pytest.',
choices: ['INFO', 'DEBUG', 'TRACE', 'WARNING', 'CRITICAL']
)
/* FIXME: This is temporary and should be removed. */
choice(
name: 'AGENT',
description: 'Agent name to run tests on it.',
choices: ['linux', 'linux-01', 'linux-02', 'linux-03', 'linux-04', 'linux-05']
)
}
options {
timestamps()
/* Prevent Jenkins jobs from running forever */
timeout(time: 120, unit: 'MINUTES')
/* manage how many builds we keep */
buildDiscarder(logRotator(
daysToKeepStr: '60',
numToKeepStr: '50',
artifactNumToKeepStr: '50',
))
}
environment {
SQUISH_DIR = '/opt/squish-runner-7.2.1'
PYTHONPATH = "${SQUISH_DIR}/lib:${SQUISH_DIR}/lib/python:${PYTHONPATH}"
LD_LIBRARY_PATH = "${SQUISH_DIR}/lib:${SQUISH_DIR}/python3/lib:${LD_LIBRARY_PATH}"
/* Avoid race conditions with other builds using virtualenv. */
VIRTUAL_ENV = "${WORKSPACE_TMP}/venv"
PATH = "${VIRTUAL_ENV}/bin:${PATH}"
TESTRAIL_URL = 'https://ethstatus.testrail.net'
TESTRAIL_PROJECT_ID = 17
/* Runtime flag to make testing of the app easier. Switched off: unpredictable app behavior under new tests */
/* STATUS_RUNTIME_TEST_MODE = 'True' */
}
stages {
stage('Prep') {
steps { script {
setNewBuildName()
updateGitHubStatus()
} }
}
stage('Deps') {
steps { script {
sh "python3 -m venv ${VIRTUAL_ENV}"
sh 'pip3 install -r requirements.txt'
} }
}
stage('Download') {
when { expression { params.BUILD_SOURCE.startsWith('http') } }
steps { timeout(5) { script {
sh 'mkdir -p ./pkg/'
setBuildDescFromFile(params.BUILD_SOURCE)
fileOperations([
fileDownloadOperation(
url: params.BUILD_SOURCE,
targetFileName: 'StatusIm-Desktop.tar.gz',
targetLocation: './pkg/',
userName: '',
password: '',
)
])
} } }
}
stage('Copy') {
when { expression { ! params.BUILD_SOURCE.startsWith('http') } }
steps { timeout(5) { script {
copyArtifacts(
projectName: params.BUILD_SOURCE,
filter: 'pkg/*-x86_64.tar.gz',
selector: lastWithArtifacts(),
target: './'
)
setBuildDescFromFile(utils.findFile('pkg/*tar.gz'))
} } }
}
stage('Unpack') {
steps { timeout(5) { script {
sh 'mkdir aut'
sh "tar -zxvf '${utils.findFile('pkg/*tar.gz')}' -C './aut'"
env.AUT_PATH = utils.findFile('aut/*.AppImage')
} } }
}
stage('Test') {
steps { timeout(90) { script {
def flags = []
if (params.TEST_NAME) { flags.add("-k=${params.TEST_NAME}") }
if (params.TEST_SCOPE_FLAG) { flags.add(params.TEST_SCOPE_FLAG) }
if (params.LOG_LEVEL) { flags.addAll(["--log-level=${params.LOG_LEVEL}", "--log-cli-level=${params.LOG_LEVEL}"]) }
dir ('configs') { sh 'ln -s _local.ci.py _local.py' }
wrap([
$class: 'Xvfb',
autoDisplayName: true,
parallelBuild: true,
screen: '1920x1080x24',
additionalOptions: '-dpi 1'
]) {
sh 'fluxbox &'
withCredentials([
usernamePassword(
credentialsId: 'test-rail-api-devops',
usernameVariable: 'TESTRAIL_USR',
passwordVariable: 'TESTRAIL_PSW'
)
]) {
/* Keep the --reruns flag first, or it won't work */
sh """
python3 -m pytest --reruns=1 ${flags.join(" ")} \
--disable-warnings \
--alluredir=./allure-results
"""
}
}
} } }
}
}
post {
always { script {
archiveArtifacts('aut/*.log')
/* Needed to categorize types of errors and add environment section in allure report. */
sh 'cp ext/allure_files/categories.json allure-results'
sh 'cp ext/allure_files/environment.properties allure-results'
allure([
results: [[path: 'allure-results']],
reportBuildPolicy: 'ALWAYS',
properties: [],
jdk: '',
])
updateGitHubStatus()
} }
failure { script {
discord.send(
header: '**Desktop E2E test failure!**',
cred: 'discord-status-desktop-e2e-webhook',
)
} }
cleanup { cleanWs() }
}
}
def setNewBuildName() {
if (currentBuild.upstreamBuilds) {
def parent = utils.parentOrCurrentBuild()
currentBuild.displayName = parent.getFullDisplayName().minus('status-desktop » ')
}
}
def setBuildDescFromFile(fileNameOrPath) {
def tokens = utils.parseFilename(utils.baseName(fileNameOrPath))
if (tokens == null) { /* Fallback for regex fail. */
currentBuild.description = utils.baseName(fileNameOrPath)
return
}
if (tokens.build && tokens.build.startsWith('pr')) {
currentBuild.displayName = tokens.build.replace(/^pr/, 'PR-')
}
currentBuild.description = formatMap([
Node: NODE_NAME,
Build: tokens.build,
Commit: tokens.commit,
Version: (tokens.tstamp ?: tokens.version),
])
}
def updateGitHubStatus() {
/* For PR builds update check status. */
if (params.BUILD_SOURCE ==~ /.*\/PR-[0-9]+\/?$/) {
github.statusUpdate(
context: 'jenkins/prs/tests/e2e-new',
commit: jenkins.getJobCommitByPath(params.BUILD_SOURCE),
repo_url: 'https://github.com/status-im/status-desktop'
)
}
}
def formatMap(Map data=[:]) {
def text = ''
data.each { key, val -> text += "<b>${key}</b>: ${val}</a><br>\n" }
return text
}
def getDefaultBuildSource() {
if (JOB_NAME ==~ 'status-desktop/qa-automation/prs/.*') {
return 'status-desktop/nightly'
}
return ''
}
def getDefaultTestScopeFlag() {
if (JOB_NAME == "status-desktop/systems/linux/x86_64/tests-e2e-new") {
return ''
} else {
return '-m=critical'
}
}

View File

@ -0,0 +1,28 @@
import logging
from os import path
from scripts.utils.system_path import SystemPath
from . import testpath, timeouts, testrail, squish, system
LOG = logging.getLogger(__name__)
try:
from ._local import *
except ImportError:
exit(
'Config file: "_local.py" not found in "./configs".\n'
'Please use template "_.local.default.py" to create file or execute command: \n'
rf'cp {testpath.ROOT}/configs/_local.default.py {testpath.ROOT}/configs/_local.py'
)
if AUT_PATH is None:
exit('Please add "AUT_PATH" in ./configs/_local.py')
if system.IS_WIN and 'bin' not in AUT_PATH:
exit('Please use launcher from "bin" folder in "AUT_PATH"')
AUT_PATH = SystemPath(AUT_PATH)
# Save application logs
AUT_DIR = path.dirname(AUT_PATH)
PYTEST_LOG = path.join(AUT_DIR, 'pytest.log')
AUT_LOG_FILE = path.join(AUT_DIR, 'aut.log')
SQUISH_LOG_FILE = path.join(AUT_DIR, 'squish.log')

View File

@ -0,0 +1,7 @@
import os
import logging
LOG_LEVEL = logging.getLevelName(os.getenv('LOG_LEVEL', 'INFO'))
UPDATE_VP_ON_FAIL = False
DEV_BUILD = False
AUT_PATH = os.getenv('AUT_PATH')

View File

@ -0,0 +1,6 @@
import logging
LOG_LEVEL = logging.DEBUG
UPDATE_VP_ON_FAIL = False
DEV_BUILD = False
AUT_PATH = None

View File

@ -0,0 +1,5 @@
import os
AUT_PORT = 61500 + int(os.getenv('BUILD_NUMBER', 0))
SERVER_PORT = 4322 + int(os.getenv('BUILD_NUMBER', 0))
CURSOR_ANIMATION = False

View File

@ -0,0 +1,12 @@
import os
import platform
IS_LIN = True if platform.system() == 'Linux' else False
IS_MAC = True if platform.system() == 'Darwin' else False
IS_WIN = True if platform.system() == 'Windows' else False
OS_ID = 'lin' if IS_LIN else 'mac' if IS_MAC else 'win'
DISPLAY = os.getenv('DISPLAY', ':0')
TEST_MODE = os.getenv('STATUS_RUNTIME_TEST_MODE')

View File

@ -0,0 +1,33 @@
import os
import typing
from datetime import datetime
from scripts.utils.system_path import SystemPath
ROOT: SystemPath = SystemPath(__file__).resolve().parent.parent
# Runtime initialisation
TEST: typing.Optional[SystemPath] = None
TEST_VP: typing.Optional[SystemPath] = None
TEST_ARTIFACTS: typing.Optional[SystemPath] = None
# Test Directories
RUN_ID = os.getenv('RUN_DIR', f'run_{datetime.today().strftime("%Y-%m-%d %H:%M:%S")}')
RESULTS: SystemPath = ROOT / 'local_run_results'
RUN: SystemPath = RESULTS / RUN_ID
VP: SystemPath = ROOT / 'ext' / 'vp'
TEST_FILES: SystemPath = ROOT / 'ext' / 'test_files'
TEST_IMAGES: SystemPath = ROOT / 'ext' / 'test_images'
TEST_USER_DATA: SystemPath = ROOT / 'ext' / 'user_data'
# Driver Directories
SQUISH_DIR_RAW = os.getenv('SQUISH_DIR')
assert SQUISH_DIR_RAW is not None
SQUISH_DIR = SystemPath(SQUISH_DIR_RAW)
# Status Application
STATUS_DATA: SystemPath = RUN
# Sets log level, can be one of: "ERROR", "WARN", "INFO", "DEBUG", "TRACE". "INFO"
LOG_LEVEL = 'DEBUG'

View File

@ -0,0 +1,9 @@
import os
CI_BUILD_URL = os.getenv('BUILD_URL', '')
RUN_NAME = os.getenv('TESTRAIL_RUN_NAME', '')
PROJECT_ID = os.getenv('TESTRAIL_PROJECT_ID', '')
URL = os.getenv('TESTRAIL_URL', '')
USR = os.getenv('TESTRAIL_USR', '')
PSW = os.getenv('TESTRAIL_PSW', '')

View File

@ -0,0 +1,7 @@
# Timoeuts before raising errors
UI_LOAD_TIMEOUT_SEC = 5
UI_LOAD_TIMEOUT_MSEC = UI_LOAD_TIMEOUT_SEC * 1000
PROCESS_TIMEOUT_SEC = 10
APP_LOAD_TIMEOUT_MSEC = 60000
MESSAGING_TIMEOUT_SEC = 60

70
test/e2e/conftest.py Normal file
View File

@ -0,0 +1,70 @@
import logging
from datetime import datetime
import os
import allure
import pytest
from PIL import ImageGrab
import configs
from configs.system import IS_LIN
from fixtures.path import generate_test_info
from scripts.utils.system_path import SystemPath
# Send logs to pytest.log as well
handler = logging.FileHandler(filename=configs.PYTEST_LOG)
logging.basicConfig(
level=os.getenv('LOG_LEVEL', 'INFO'),
format='[%(asctime)s] (%(filename)18s:%(lineno)-3s) [%(levelname)-7s] --- %(message)s',
handlers=[handler],
)
LOG = logging.getLogger(__name__)
pytest_plugins = [
'fixtures.aut',
'fixtures.path',
'fixtures.squish',
'fixtures.testrail',
]
@pytest.fixture(scope='session', autouse=True)
def setup_session_scope(
init_testrail_api,
prepare_test_directory,
start_squish_server,
):
LOG.info('Session startup...')
yield
@pytest.fixture(autouse=True)
def setup_function_scope(
caplog,
generate_test_data,
check_result,
application_logs
):
# FIXME: broken due to KeyError: <_pytest.stash.StashKey object at 0x7fd1ba6d78c0>
# caplog.set_level(configs.LOG_LEVEL)
yield
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
rep = outcome.get_result()
setattr(item, 'rep_' + rep.when, rep)
def pytest_exception_interact(node):
test_path, test_name, test_params = generate_test_info(node)
node_dir: SystemPath = configs.testpath.RUN / test_path / test_name / test_params
node_dir.mkdir(parents=True, exist_ok=True)
screenshot = node_dir / f'screenshot_{datetime.today().strftime("%Y-%m-%d %H:%M:%S")}.png'
ImageGrab.grab(xdisplay=configs.system.DISPLAY if IS_LIN else None).save(screenshot)
allure.attach(
name='Screenshot on fail',
body=screenshot.read_bytes(),
attachment_type=allure.attachment_type.PNG
)

View File

@ -0,0 +1,5 @@
from . import commands
from .colors import *
from .social_links import *
from .tesseract import *
from .user import *

View File

@ -0,0 +1 @@
MOCK_KEYCARD = '--test-mode=1'

View File

@ -0,0 +1,61 @@
from enum import Enum
class Color(Enum):
WHITE = 1
BLACK = 2
RED = 3
BLUE = 4
GREEN = 5
YELLOW = 6
ORANGE = 7
boundaries = {
Color.WHITE: [
[0, 0, 0],
[0, 0, 255]
],
Color.BLACK: [
[0, 0, 0],
[179, 100, 130]
],
Color.RED: [
[
[0, 100, 20],
[10, 255, 255]
],
[
[160, 100, 20],
[179, 255, 255]
]
],
Color.BLUE: [
[110, 50, 50],
[130, 255, 255]
],
Color.GREEN: [
[36, 25, 25],
[70, 255, 255]
],
Color.YELLOW: [
[20, 100, 0],
[45, 255, 255]
],
Color.ORANGE: [
[10, 100, 20],
[25, 255, 255]
]
}
class ColorCodes(Enum):
GREEN = '#4ebc60'
BLUE = '#2a4af5'
ORANGE = '#ff9f0f'
GRAY = '#939ba1'
class EmojiCodes(Enum):
SMILING_FACE_WITH_SUNGLASSES = '1f60e'
THUMBSUP_SIGN = '1f44d'

View File

@ -0,0 +1,13 @@
import configs.system
# Buttons
BACKSPACE = 'Backspace'
COMMAND = 'Command'
CTRL = 'Ctrl'
ESCAPE = 'Escape'
RETURN = 'Return'
SHIFT = 'Shift'
# Combinations
SELECT_ALL = f'{CTRL if configs.system.IS_WIN else COMMAND}+A'
OPEN_GOTO = f'{COMMAND}+{SHIFT}+G'

View File

@ -0,0 +1,60 @@
from enum import Enum
class PermissionsElements(Enum):
WELCOME_TITLE = "Permissions"
WELCOME_SUBTITLE = 'You can manage your community by creating and issuing membership and access permissions'
WELCOME_CHECKLIST_ELEMENT_1 = 'Give individual members access to private channels'
WELCOME_CHECKLIST_ELEMENT_2 = 'Monetise your community with subscriptions and fees'
WELCOME_CHECKLIST_ELEMENT_3 = 'Require holding a token or NFT to obtain exclusive membership rights'
class TokensElements(Enum):
WELCOME_TITLE = "Community tokens"
WELCOME_SUBTITLE = 'You can mint custom tokens and import tokens for your community'
WELCOME_CHECKLIST_ELEMENT_1 = 'Create remotely destructible soulbound tokens for admin permissions'
WELCOME_CHECKLIST_ELEMENT_2 = 'Reward individual members with custom tokens for their contribution'
WELCOME_CHECKLIST_ELEMENT_3 = 'Mint tokens for use with community and channel permissions'
INFOBOX_TITLE = 'Get started'
INFOBOX_TEXT = 'In order to Mint, Import and Airdrop community tokens, you first need to mint your Owner token which will give you permissions to access the token management features for your community.'
class MintOwnerTokensElements(Enum):
OWNER_TOKEN_CHEKLIST_ELEMENT_1 = 'Only 1 will ever exist'
OWNER_TOKEN_CHEKLIST_ELEMENT_2 = 'Hodler is the owner of the Community'
OWNER_TOKEN_CHEKLIST_ELEMENT_3 = 'Ability to airdrop / destroy TokenMaster token'
OWNER_TOKEN_CHEKLIST_ELEMENT_4 = 'Ability to mint and airdrop Community tokens'
MASTER_TOKEN_CHEKLIST_ELEMENT_1 = 'Unlimited supply'
MASTER_TOKEN_CHEKLIST_ELEMENT_2 = 'Grants full Community admin rights'
MASTER_TOKEN_CHEKLIST_ELEMENT_3 = 'Ability to mint and airdrop Community tokens'
MASTER_TOKEN_CHEKLIST_ELEMENT_4 = 'Non-transferrable'
MASTER_TOKEN_CHEKLIST_ELEMENT_5 = 'Remotely destructible by the Owner token hodler'
SIGN_TRANSACTION_MINT_TITLE = ' Owner and TokenMaster tokens on Mainnet'
OWNER_TOKEN_NAME = 'Owner-'
MASTER_TOKEN_NAME = 'TMaster-'
OWNER_TOKEN_SYMBOL = 'OWN'
MASTER_TOKEN_SYMBOL = 'TM'
TOAST_AIRDROPPING_TOKEN_1 = 'Airdropping '
TOAST_AIRDROPPING_TOKEN_2 = ' Owner token to you...'
TOAST_TOKENS_BEING_MINTED = ' Owner and TokenMaster tokens are being minted...'
class AirdropsElements(Enum):
WELCOME_TITLE = "Airdrop community tokens"
WELCOME_SUBTITLE = 'You can mint custom tokens and collectibles for your community'
WELCOME_CHECKLIST_ELEMENT_1 = 'Reward individual members with custom tokens for their contribution'
WELCOME_CHECKLIST_ELEMENT_2 = 'Incentivise joining, retention, moderation and desired behaviour'
WELCOME_CHECKLIST_ELEMENT_3 = 'Require holding a token or NFT to obtain exclusive membership rights'
INFOBOX_TITLE = 'Get started'
INFOBOX_TEXT = 'In order to Mint, Import and Airdrop community tokens, you first need to mint your Owner token which will give you permissions to access the token management features for your community.'
class ToastMessages(Enum):
CREATE_PERMISSION_TOAST = 'Community permission created'
UPDATE_PERMISSION_TOAST = 'Community permission updated'
DELETE_PERMISSION_TOAST = 'Community permission deleted'
KICKED_USER_TOAST = ' was kicked from '
class LimitWarnings(Enum):
MEMBER_ROLE_LIMIT_WARNING = 'Max of 5 become member permissions for this Community has been reached. You will need to delete an existing become member permission before you can add a new one.'

View File

@ -0,0 +1,18 @@
# Images paths
PERMISSION_WELCOME_IMAGE_PATH = '/imports/assets/png/community/permissions2_3.png'
AIRDROPS_WELCOME_IMAGE_PATH = '/imports/assets/png/community/airdrops8_1.png'
TOKENS_WELCOME_IMAGE_PATH = '/imports/assets/png/community/mint2_1.png'
PLUG_IN_KEYCARD_IMAGE_PATH = '/imports/assets/png/keycard/empty-reader.png'
INSERT_KEYCARD_IMAGE_PATH = '/imports/assets/png/keycard/card_insert/img-15.png'
EMPTY_KEYCARD_IMAGE_PATH = '/imports/assets/png/keycard/card-empty.png'
KEYCARD_INSERTED_IMAGE_PATH = '/imports/assets/png/keycard/card-inserted.png'
CHOOSE_KEYCARD_PIN_IMAGE_PATH = '/imports/assets/png/keycard/enter-pin-0.png'
KEYCARD_SUCCESS_IMAGE_PATH = '/imports/assets/png/keycard/strong_success/img-20.png'
KEYCARD_RECOGNIZED_IMAGE_PATH = '/imports/assets/png/keycard/success/img-13.png'
KEYCARD_ERROR_IMAGE_PATH = '/imports/assets/png/keycard/plain-error.png'
HEART_EMOJI_PATH = '/imports/assets/icons/emojiReactions/heart.svg'
THUMBSUP_EMOJI_PATH = '/imports/assets/icons/emojiReactions/thumbsUp.svg'
THUMBSDOWN_EMOJI_PATH = '/imports/assets/icons/emojiReactions/thumbsDown.svg'
LAUGHING_EMOJI_PATH = '/imports/assets/icons/emojiReactions/laughing.svg'
SAD_EMOJI_PATH = '/imports/assets/icons/emojiReactions/sad.svg'
ANGRY_EMOJI_PATH = '/imports/assets/icons/emojiReactions/angry.svg'

View File

@ -0,0 +1,54 @@
from enum import Enum
class Keycard(Enum):
KEYCARD_PIN = '111111'
KEYCARD_INCORRECT_PIN = '222222'
KEYCARD_CORRECT_PUK = '111111111111'
KEYCARD_INCORRECT_PUK = '222222222222'
KEYCARD_NAME = 'Test Keycard'
ACCOUNT_NAME = 'Test Account'
KEYCARD_POPUP_HEADER_CREATE_SEED = 'Create a new Keycard account with a new seed phrase'
KEYCARD_POPUP_HEADER_IMPORT_SEED = 'Import or restore a Keycard via a seed phrase'
KEYCARD_POPUP_HEADER_SET_UP_EXISTING = 'Set up a new Keycard with an existing account'
KEYCARD_INSTRUCTIONS_PLUG_IN = 'Plug in Keycard reader...'
KEYCARD_INSTRUCTIONS_INSERT_KEYCARD = 'Insert Keycard...'
KEYCARD_RECOGNIZED = 'Keycard recognized'
KEYCARD_CHOOSE_PIN = 'Choose a Keycard PIN'
KEYCARD_ENTER_PIN = "Enter this Keycards PIN"
KEYCARD_ENTER_PIN_2 = "Enter Keycard PIN"
KEYCARD_PIN_NOTE = 'It is very important that you do not lose this PIN'
KEYCARD_REPEAT_PIN = 'Repeat Keycard PIN'
KEYCARD_PIN_SET = 'Keycard PIN set'
KEYCARD_PIN_VERIFIED = 'Keycard PIN verified!'
KEYCARD_NAME_KEYCARD = 'Name this Keycard'
KEYCARD_NAME_ACCOUNTS = 'Name accounts'
KEYCARD_NEW_ACCOUNT_CREATED = 'New account successfully created'
KEYCARD_READY = 'Keycard is ready to use!'
KEYCARD_SELECT_KEYPAIR = 'Select a key pair'
KEYCARD_SELECT_WHICH_PAIR = 'Select which key pair youd like to move to this Keycard'
KEYCARD_KEYPAIR_INFO = 'Moving this key pair will require you to use your Keycard to login'
KEYCARD_MIGRATING = 'Migrating key pair to Keycard'
KEYCARD_KEYPAIR_MIGRATED = 'Keypair successfully migrated'
KEYCARD_COMPLETE_MIGRATION = 'To complete migration close Status and log in with your new Keycard'
KEYCARD_EMPTY = 'Keycard is empty'
KEYCARD_NO_KEYPAIR = 'There is no key pair on this Keycard'
KEYCARD_NOT = 'This is not a Keycard'
KEYCARD_NOT_RECOGNIZED_NOTE = 'The card inserted is not a recognised Keycard,\nplease remove and try and again'
KEYCARD_LOCKED = 'Keycard locked'
KEYCARD_LOCKED_NOTE = 'You will need to unlock it before proceeding'
KEYCARD_ACCOUNTS = 'Accounts on this Keycard'
KEYCARD_FACTORY_RESET_TITLE = 'A factory reset will delete the key on this Keycard.\nAre you sure you want to do this?'
KEYCARD_FACTORY_RESET_SUCCESSFUL = 'Keycard successfully factory reset'
KEYCARD_YOU_CAN_USE_AS_EMPTY = 'You can now use this Keycard as if it\nwas a brand new empty Keycard'
KEYCARD_INCORRECT_PIN_MESSAGE = 'PIN incorrect'
KEYCARD_4_ATTEMPTS_REMAINING = '4 attempts remaining'
KEYCARD_3_ATTEMPTS_REMAINING = '3 attempts remaining'
KEYCARD_2_ATTEMPTS_REMAINING = '2 attempts remaining'
KEYCARD_1_ATTEMPT_REMAINING = '1 attempt remaining'
KEYCARD_LOCKED_INCORRECT_PIN = 'Pin entered incorrectly too many times'
KEYCARD_LOCKED_INCORRECT_PUK = 'Puk entered incorrectly too many times'
KEYCARD_UNLOCK = 'Unlock this Keycard'
KEYCARD_ENTER_PUK = 'Enter PUK'
KEYCARD_UNLOCK_SUCCESSFUL = 'Unlock successful'
KEYCARD_PUK_IS_INCORRECT = 'The PUK is incorrect, try entering it again'

View File

@ -0,0 +1,4 @@
# Links for link preview tests
external_link = 'https://github.com/status-im/status-desktop/issues/12018'
link_to_status_community = 'https://status.app/c/G4IAAMSIuU08Lm3oHzSz695ImidijVBxyoFDGEiSYAvADsk9ZVOKYlT2b-lHStyz1MqqkK2Xa4FwoUiq3LBgWsYI_ht6hWXCyLu0TGAk0dGu8IyQWtDSdIXOQ3hWscLjkTo5Vg5-eyUuV8jOVv7khJ_uTofT_TijN-sB#zQ3shZeEJqTC1xhGUjxuS4rtHSrhJ8vUYp64v6qWkLpvdy9L9'
status_user_profile_link = 'https://status.app/u/iwWACgoKCHNxdWlzaGVyAw==#zQ3shvMPZSyaUbjBjaNpNP1bPGsGpQDp59dZ4Gmz7UEy5o791'

View File

@ -0,0 +1,14 @@
from enum import Enum
class Messaging(Enum):
WELCOME_GROUP_MESSAGE = "Welcome to the beginning of the "
CONTACT_REQUEST_SENT = 'Contact Request Sent'
NO_FRIENDS_ITEM = 'You dont have any contacts yet'
NEW_CONTACT_REQUEST = 'New Contact Request'
MESSAGE_NOTE_IDENTITY_REQUEST = 'Ask a question only they can answer'
YOU_NEED_TO_BE_A_MEMBER = 'You need to be a member of this group to send messages'
ID_VERIFICATION_REQUEST_SENT = 'ID verification request sent'
ID_VERIFICATION_REPLY_SENT = 'ID verification reply sent'
SHOW_PREVIEWS_TITLE = 'Show link previews?'
SHOW_PREVIEWS_TEXT = 'A preview of your link will be shown here before you send it'

View File

@ -0,0 +1,35 @@
from collections import namedtuple
from enum import Enum
class OnboardingMessages(Enum):
WRONG_LOGIN_LESS_LETTERS = 'Display Names must be at least 5 character(s) long'
WRONG_LOGIN_SYMBOLS_NOT_ALLOWED = 'Invalid characters (use A-Z and 0-9, hyphens and underscores only)'
WRONG_PASSWORD = 'Password must be at least 10 characters long'
PASSWORDS_DONT_MATCH = "Passwords don't match"
PASSWORD_INCORRECT = 'Password incorrect'
class OnboardingScreensHeaders(Enum):
YOUR_EMOJIHASH_AND_IDENTICON_RING_SCREEN_TITLE = 'Your emojihash and identicon ring'
YOUR_PROFILE_SCREEN_TITLE = 'Your profile'
class KeysExistText(Enum):
KEYS_EXIST_TITLE = 'Keys for this account already exist'
KEYS_EXIST_TEXT = (
"Keys for this account already exist and can't be added again. If you've lost your password, passcode or Keycard, uninstall the app, reinstall and access your keys by entering your seed phrase. In case of Keycard try recovering using PUK or reinstall the app and try login with the Keycard option.")
password_strength_elements = namedtuple('Password_Strength_Elements',
['strength_indicator', 'strength_color', 'strength_messages'])
very_weak_lower_elements = password_strength_elements('Very weak', '#ff2d55', ['• Lower case'])
very_weak_upper_elements = password_strength_elements('Very weak', '#ff2d55', ['• Upper case'])
very_weak_numbers_elements = password_strength_elements('Very weak', '#ff2d55', ['• Numbers'])
very_weak_symbols_elements = password_strength_elements('Very weak', '#ff2d55', ['• Symbols'])
weak_elements = password_strength_elements('Weak', '#fe8f59', ['• Numbers', '• Symbols'])
so_so_elements = password_strength_elements('So-so', '#ffca0f', ['• Lower case', '• Numbers', '• Symbols'])
good_elements = password_strength_elements('Good', '#9ea85d',
['• Lower case', '• Upper case', '• Numbers', '• Symbols'])
great_elements = password_strength_elements('Great', '#4ebc60',
['• Lower case', '• Upper case', '• Numbers', '• Symbols'])

View File

@ -0,0 +1,5 @@
from enum import Enum
class PasswordView(Enum):
RESTART_STATUS = 'Restart Status'

View File

@ -0,0 +1,3 @@
# List of social links
social_links = ['testerTwitter', 'status.im', 'testerGithub', 'testerTube', 'testerDiscord', 'testerTelegram',
'customLink', 'https://status.im/']

View File

@ -0,0 +1,9 @@
from enum import Enum
class SyncingSettings(Enum):
SYNC_A_NEW_DEVICE_INSTRUCTIONS_HEADER = 'Sync a New Device'
SYNC_A_NEW_DEVICE_INSTRUCTIONS_SUBTITLE = 'You own your data. Sync it among your devices.'
SYNC_CODE_IS_WRONG_TEXT = 'This does not look like a sync code'
SYNC_SETUP_ERROR_PRIMARY = 'Failed to generate sync code'
SYNC_SETUP_ERROR_SECONDARY = 'Failed to start pairing server'

View File

@ -0,0 +1,30 @@
"""
Tesseract provides various configuration parameters that can be used to customize the OCR process. These parameters are passed as command-line arguments to Tesseract through the --oem and --psm options or through the config parameter in pytesseract. Here are some commonly used Tesseract configuration parameters:
--oem (OCR Engine Mode): This parameter specifies the OCR engine mode to use. The available options are:
0: Original Tesseract only.
1: Neural nets LSTM only.
2: Tesseract + LSTM.
3: Default, based on what is available.
--psm (Page Segmentation Mode): This parameter defines the page layout analysis mode to use. The available options are:
0: Orientation and script detection (OSD) only.
1: Automatic page segmentation with OSD.
2: Automatic page segmentation, but no OSD or OCR.
3: Fully automatic page segmentation, but no OSD. (Default)
4: Assume a single column of text of variable sizes.
5: Assume a single uniform block of vertically aligned text.
6: Assume a single uniform block of text.
7: Treat the image as a single text line.
8: Treat the image as a single word.
9: Treat the image as a single word in a circle.
10: Treat the image as a single character.
--lang (Language): This parameter specifies the language(s) to use for OCR. Multiple languages can be specified separated by plus (+) signs. For example, --lang eng+fra for English and French.
--tessdata-dir (Tessdata Directory): This parameter sets the path to the directory containing Tesseract's language data files.
These are just a few examples of the commonly used configuration parameters in Tesseract. There are many more options available for advanced customization and fine-tuning of OCR results. You can refer to the official Tesseract documentation for a comprehensive list of configuration parameters and their descriptions: https://tesseract-ocr.github.io/tessdoc/Command-Line-Usage.html
"""
text_on_profile_image = r'--oem 3 --psm 10'

View File

@ -0,0 +1,96 @@
import random
import string
from collections import namedtuple
import configs
UserAccount = namedtuple('User', ['name', 'password', 'seed_phrase', 'status_address'])
user_account_one = UserAccount('squisher', '0000000000', [
'rail', 'witness', 'era', 'asthma', 'empty', 'cheap', 'shed', 'pond', 'skate', 'amount', 'invite', 'year'
], '0x3286c371ef648fe6232324b27ee0515f4ded24d9')
user_account_two = UserAccount('athletic', '0000000000', [
'measure', 'cube', 'cousin', 'debris', 'slam', 'ignore', 'seven', 'hat', 'satisfy', 'frown', 'casino', 'inflict'
], '0x99C096bB5F12bDe37DE9dbee8257Ebe2a5667C46')
user_account_three = UserAccount('nervous', '0000000000', [], '')
# users for group chat test
group_chat_user_1 = UserAccount('group_chat_user_1', '77_80Y+2Eh', [
'trophy', 'math', 'robust', 'lake', 'extend', 'cabbage', 'bicycle', 'begin', 'either', 'car', 'race', 'cousin'], '0xcd488381c1664c9585b7940f1c4b20f884b8b4a9')
group_chat_user_2 = UserAccount('group_chat_user_2', '521/97Qv\:', [
'opera', 'great', 'open', 'sight', 'still', 'quantum', 'flight', 'torch', 'mule', 'cage', 'noise', 'horn'
], '0x472078f0110d0bb0dfc73389ce89d8a83c8c0502')
group_chat_user_3 = UserAccount('group_chat_user_3', '29T\I8Cv_G', [
'bless', 'enter', 'wet', 'foot', 'lazy', 'will', 'reform', 'enemy', 'rubber', 'void', 'journey', 'fence'
], '0x4b04b8e22e8295d0ae3177774e4acfd0badacf09')
# usernames and passwords for join community test
community_user_1 = UserAccount('community_user_1', '|Br2w547YN', [
'skirt', 'tired', 'finger', 'dinosaur', 'equal', 'garlic', 'snap', 'tired', 'friend', 'rack', 'net', 'imitate'
], '0x21371358f1ba09204475e87444962ea4519771e1')
community_user_2 = UserAccount('community_user_2', 'vSq5T702_p', [
'will', 'horn', 'tail', 'stock', 'puzzle', 'warfare', 'pledge', 'uniform', 'ozone', 'taste', 'someone', 'silk'
], '0x935034600f2ba486324cee6ae3f96ad8c8915ac6')
user_with_random_attributes_1 = UserAccount(
''.join((random.choice(
string.ascii_letters + string.digits + random.choice('_- '))
for i in range(5, 25))
).strip(' '),
''.join((random.choice(
string.ascii_letters + string.digits + string.punctuation)
for _ in range(10, 28))
), [], ''
)
user_with_random_attributes_2 = UserAccount(
''.join((random.choice(
string.ascii_letters + string.digits + random.choice('_- '))
for i in range(5, 25))
).strip(' '),
''.join((random.choice(
string.ascii_letters + string.digits + string.punctuation)
for _ in range(10, 28))
), [], ''
)
user_account_one_changed_password = UserAccount('squisher', 'NewPassword@12345', [], '')
user_account_one_changed_name = UserAccount('NewUserName', '0000000000', [], '')
user_with_funds = UserAccount('User_with_funds', '0000000000', [
'vocal', 'fruit', 'ordinary', 'meadow', 'south', 'athlete', 'inherit', 'since', 'version', 'pitch', 'oppose',
'lonely'
], '0x26d6e10a6af4eb4d12ff4cf133a843eb4fa88d0b')
community_params = {
'name': ''.join(random.choices(string.ascii_letters +
string.digits, k=30)),
'description': ''.join(random.choices(string.ascii_letters +
string.digits, k=140)),
'logo': {'fp': configs.testpath.TEST_IMAGES / 'comm_logo.jpeg', 'zoom': None, 'shift': None},
'banner': {'fp': configs.testpath.TEST_IMAGES / 'comm_banner.jpeg', 'zoom': None, 'shift': None},
'intro': ''.join(random.choices(string.ascii_letters +
string.digits, k=200)),
'outro': ''.join(random.choices(string.ascii_letters +
string.digits, k=80))
}
UserCommunityInfo = namedtuple('CommunityInfo', ['name', 'description', 'members', 'image'])
UserChannel = namedtuple('Channel', ['name', 'selected', 'visible'])
account_list_item = namedtuple('AccountListItem', ['name', 'color', 'emoji'])
wallet_account_list_item = namedtuple('WalletAccountListItem', ['name', 'icon_color', 'icon_emoji', 'object'])
account_list_item_2 = namedtuple('AccountListItem', ['name2', 'color2', 'emoji2'])
wallet_account_list_item_2 = namedtuple('WalletAccountListItem', ['name', 'icon', 'object'])
wallet_account = namedtuple('PrivateKeyAddressPair', ['private_key', 'wallet_address'])
private_key_address_pair_1 = wallet_account('2daa36a3abe381a9c01610bf10fda272fbc1b8a22179a39f782c512346e3e470',
'0xd89b48cbcb4244f84a4fb5d3369c120e8f8aa74e')
token_list_item = namedtuple('TokenListItem', ['title', 'object'])
ens_user_name = ''.join(
random.choices(string.digits + string.ascii_lowercase, k=8))
community_tags = ['Activism', 'Art', 'Blockchain', 'Books & blogs', 'Career', 'Collaboration', 'Commerce', 'Culture', 'DAO', 'DIY', 'DeFi', 'Design', 'Education', 'Entertainment', 'Environment', 'Ethereum', 'Event', 'Fantasy', 'Fashion', 'Food', 'Gaming', 'Global', 'Health', 'Hobby', 'Innovation', 'Language', 'Lifestyle', 'Local', 'Love', 'Markets', 'Movies & TV', 'Music', 'NFT', 'NSFW', 'News', 'Non-profit', 'Org', 'Pets', 'Play', 'Podcast', 'Politics', 'Privacy', 'Product', 'Psyche', 'Security', 'Social', 'Software dev', 'Sports', 'Tech', 'Travel', 'Vehicles', 'Web3']

View File

@ -0,0 +1,77 @@
from enum import Enum
class DerivationPath(Enum):
CUSTOM = 'Custom'
ETHEREUM = 'Ethereum'
ETHEREUM_ROPSTEN = 'Ethereum Testnet (Ropsten)'
ETHEREUM_LEDGER = 'Ethereum (Ledger)'
ETHEREUM_LEDGER_LIVE = 'Ethereum (Ledger Live/KeepKey)'
STATUS_ACCOUNT_DERIVATION_PATH = "m / 44' / 60' / 0' / 0 / 0"
GENERATED_ACCOUNT_DERIVATION_PATH_1 = "m / 44' / 60' / 0' / 0 / 1"
class WalletNetworkSettings(Enum):
EDIT_NETWORK_LIVE_TAB = 'Live Network'
EDIT_NETWORK_TEST_TAB = 'Test Network'
TESTNET_SUBTITLE = 'Switch entire Status app to testnet only mode'
TESTNET_ENABLED_TOAST_MESSAGE = 'Testnet mode turned on'
TESTNET_DISABLED_TOAST_MESSAGE = 'Testnet mode turned off'
ACKNOWLEDGMENT_CHECKBOX_TEXT = ('I understand that changing network settings can cause unforeseen issues, errors, '
'security risks and potentially even loss of funds.')
REVERT_TO_DEFAULT_LIVE_MAINNET_TOAST_MESSAGE = 'Live network settings for Mainnet reverted to default'
REVERT_TO_DEFAULT_TEST_MAINNET_TOAST_MESSAGE = 'Test network settings for Mainnet reverted to default'
STATUS_ACCOUNT_DEFAULT_NAME = 'Account 1'
STATUS_ACCOUNT_DEFAULT_COLOR = '#2a4af5'
class WalletAccountSettings(Enum):
STATUS_ACCOUNT_ORIGIN = 'Derived from your default Status keypair'
WATCHED_ADDRESS_ORIGIN = 'Watched address'
STORED_ON_DEVICE = 'On device'
WATCHED_ADDRESSES_KEYPAIR_LABEL = 'Watched addresses'
class WalletNetworkNaming(Enum):
LAYER1_ETHEREUM = 'Mainnet'
LAYER2_OPTIMISIM = 'Optimism'
LAYER2_ARBITRUM = 'Arbitrum'
ETHEREUM_MAINNET_NETWORK_ID = 1
ETHEREUM_SEPOLIA_NETWORK_ID = 11155111
OPTIMISM_MAINNET_NETWORK_ID = 10
OPTIMISM_SEPOLIA_NETWORK_ID = 11155420
ARBITRUM_MAINNET_NETWORK_ID = 42161
ARBITRUM_SEPOLIA_NETWORK_ID = 421614
class WalletNetworkDefaultValues(Enum):
ETHEREUM_LIVE_MAIN = 'https://eth-archival.rpc.grove.city'
ETHEREUM_TEST_MAIN = 'https://sepolia-archival.rpc.grove.city'
ETHEREUM_LIVE_FAILOVER = 'https://mainnet.infura.io'
ETHEREUM_TEST_FAILOVER = 'https://sepolia.infura.io'
class WalletEditNetworkErrorMessages(Enum):
PINGUNSUCCESSFUL = 'RPC appears to be either offline or this is not a valid JSON RPC endpoint URL'
PINGVERIFIED = 'RPC successfully reached'
class WalletOrigin(Enum):
WATCHED_ADDRESS_ORIGIN = 'New watched address'
class WalletTransactions(Enum):
TRANSACTION_PENDING_TOAST_MESSAGE = 'Transaction pending'
class WalletScreensHeaders(Enum):
WALLET_ADD_ACCOUNT_POPUP_TITLE = 'Add a new account'
WALLET_EDIT_ACCOUNT_POPUP_TITLE = 'Edit account'
class WalletRenameKeypair(Enum):
WALLET_SUCCESSFUL_RENAMING = 'You successfully renamed your keypair\n'
class WalletSeedPhrase(Enum):
WALLET_SEED_PHRASE_ALREADY_ADDED = 'The entered seed phrase is already added'

26
test/e2e/driver/__init__.py Executable file
View File

@ -0,0 +1,26 @@
import squishtest # noqa
from . import server, context, objects_access, toplevel_window, aut, mouse
from .squish_api import *
imports = {module.__name__: module for module in [
aut,
context,
objects_access,
mouse,
server,
toplevel_window
]}
def __getattr__(name):
if name in imports:
return imports[name]
try:
return getattr(squishtest, name)
except AttributeError:
raise ImportError(f'Module "driver" has no attribute "{name}"')
squishtest.testSettings.waitForObjectTimeout = configs.timeouts.UI_LOAD_TIMEOUT_MSEC
squishtest.setHookSubprocesses(True)

149
test/e2e/driver/aut.py Normal file
View File

@ -0,0 +1,149 @@
import allure
import logging
import cv2
import numpy as np
import squish
from PIL import ImageGrab
import configs
import driver
import shortuuid
from datetime import datetime
from configs.system import IS_LIN
from driver import context
from driver.server import SquishServer
from gui.objects_map.names import statusDesktop_mainWindow
from scripts.utils import system_path, local_system
from scripts.utils.system_path import SystemPath
from scripts.utils.wait_for_port import wait_for_port
LOG = logging.getLogger(__name__)
class AUT:
def __init__(
self,
app_path: system_path.SystemPath = configs.AUT_PATH,
user_data: SystemPath = None
):
self.path = app_path
self.ctx = None
self.pid = None
self.port = None
self.aut_id = f'AUT_{datetime.now():%H%M%S}'
self.app_data = configs.testpath.STATUS_DATA / f'app_{shortuuid.ShortUUID().random(length=10)}'
if user_data is not None:
user_data.copy_to(self.app_data / 'data')
self.options = ''
driver.testSettings.setWrappersForApplication(self.aut_id, ['Qt'])
def __str__(self):
return type(self).__qualname__
def __enter__(self):
return self.launch()
def __exit__(self, exc_type, exc_value, traceback):
if exc_type:
try:
self.attach()
driver.waitForObjectExists(statusDesktop_mainWindow).setVisible(True)
configs.testpath.TEST.mkdir(parents=True, exist_ok=True)
screenshot = configs.testpath.TEST / f'{self.aut_id}.png'
rect = driver.object.globalBounds(driver.waitForObject(statusDesktop_mainWindow))
img = ImageGrab.grab(
bbox=(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height),
xdisplay=configs.system.DISPLAY if IS_LIN else None)
view = cv2.cvtColor(np.array(img), cv2.COLOR_BGR2RGB)
cv2.imwrite(str(screenshot), view)
allure.attach(
name=f'Screenshot on fail: {self.aut_id}',
body=screenshot.read_bytes(),
attachment_type=allure.attachment_type.PNG)
except Exception as err:
LOG.error(err)
self.stop()
def detach_context(self):
if self.ctx is None:
return
squish.currentApplicationContext().detach()
self.ctx = None
def kill_process(self):
if self.pid is None:
LOG.warning('No PID available for AUT.')
return
local_system.kill_process_with_retries(self.pid)
self.pid = None
@allure.step('Attach Squish to Test Application')
def attach(self, timeout_sec: int = configs.timeouts.PROCESS_TIMEOUT_SEC):
LOG.info('Attaching to AUT: localhost:%d', self.port)
try:
SquishServer().add_attachable_aut(self.aut_id, self.port)
if self.ctx is None:
self.ctx = context.get_context(self.aut_id)
squish.setApplicationContext(self.ctx)
assert squish.waitFor(lambda: self.ctx.isRunning, configs.timeouts.PROCESS_TIMEOUT_SEC)
except Exception as err:
LOG.error('Failed to attach AUT: %s', err)
self.stop()
raise err
LOG.info('Successfully attached AUT!')
return self
@allure.step('Start AUT')
def startaut(self):
LOG.info('Launching AUT: %s', self.path)
self.port = local_system.find_free_port(configs.squish.AUT_PORT, 100)
command = [
str(configs.testpath.SQUISH_DIR / 'bin/startaut'),
'--verbose',
f'--port={self.port}',
str(self.path),
f'--datadir={self.app_data}',
f'--LOG_LEVEL={configs.testpath.LOG_LEVEL}',
]
try:
with open(configs.AUT_LOG_FILE, "ab") as log:
self.pid = local_system.execute(command, stderr=log, stdout=log)
except Exception as err:
LOG.error('Failed to start AUT: %s', err)
self.stop()
raise err
LOG.info('Launched AUT under PID: %d', self.pid)
return self
@allure.step('Close application')
def stop(self):
LOG.info('Stopping AUT: %s', self.path)
self.detach_context()
self.kill_process()
@allure.step("Start and attach AUT")
def launch(self) -> 'AUT':
self.startaut()
self.wait()
self.attach()
return self
@allure.step('Waiting for port')
def wait(self, timeout: int = 1, retries: int = 10):
LOG.info('Waiting for AUT port localhost:%d...', self.port)
try:
wait_for_port('localhost', self.port, timeout, retries)
except TimeoutError as err:
LOG.error('Wait for AUT port timed out: %s', err)
self.stop()
raise err
LOG.info('AUT port available!')
@allure.step('Restart application')
def restart(self):
self.stop()
self.launch()

View File

@ -0,0 +1,31 @@
import logging
import time
import allure
import squish
import configs
from driver.server import SquishServer
LOG = logging.getLogger(__name__)
@allure.step('Get application context of "{0}"')
def get_context(aut_id: str):
LOG.info('Attaching to: %s', aut_id)
try:
context = squish.attachToApplication(aut_id, SquishServer().host, SquishServer().port)
if context is not None:
LOG.info('AUT %s context found', aut_id)
return context
except RuntimeError as error:
LOG.error('AUT %s context has not been found', aut_id)
raise error
@allure.step('Detaching')
def detach():
for ctx in squish.applicationContextList():
ctx.detach()
assert squish.waitFor(lambda: not ctx.isRunning, configs.timeouts.APP_LOAD_TIMEOUT_MSEC)
LOG.info('All AUTs detached')

44
test/e2e/driver/mouse.py Executable file
View File

@ -0,0 +1,44 @@
import time
import squish
def move(obj: object, x: int, y: int, dx: int, dy: int, step: int, sleep: float = 0):
while True:
if x > dx:
x -= step
if x < x:
x = dx
elif x < dx:
x += step
if x > dx:
x = dx
if y > dy:
y -= step
if y < dy:
y = dy
elif y < dy:
y += step
if y > dy:
y = dy
squish.mouseMove(obj, x, y)
time.sleep(sleep)
if x == dx and y == dy:
break
def press_and_move(
obj,
x: int,
y: int,
dx: int,
dy: int,
mouse: int = squish.MouseButton.LeftButton,
step: int = 1,
sleep: float = 0
):
squish.mouseMove(obj, x, y)
squish.mousePress(obj, x, y, mouse)
move(obj, x, y, dx, dy, step, sleep)
squish.mouseRelease(mouse)
time.sleep(1)

View File

@ -0,0 +1,32 @@
import logging
import time
import object
import squish
import configs
LOG = logging.getLogger(__name__)
def walk_children(parent, depth: int = 1000):
for child in object.children(parent):
yield child
if depth:
yield from walk_children(child, depth - 1)
def wait_for_template(
real_name_template: dict, value: str, attr_name: str, timeout_sec: int = configs.timeouts.UI_LOAD_TIMEOUT_SEC):
started_at = time.monotonic()
while True:
for obj in squish.findAllObjects(real_name_template):
values = []
if hasattr(obj, attr_name):
current_value = str(getattr(obj, attr_name))
if current_value == value:
return obj
values.append(current_value)
if time.monotonic() - started_at > timeout_sec:
raise RuntimeError(f'Value not found in: {values}')
time.sleep(1)

69
test/e2e/driver/server.py Normal file
View File

@ -0,0 +1,69 @@
import logging
import typing
import configs.testpath
from scripts.utils import local_system
from scripts.utils.wait_for_port import wait_for_port
_PROCESS_NAME = '_squishserver'
LOG = logging.getLogger(__name__)
class SquishServer:
__instance = None
path = configs.testpath.SQUISH_DIR / 'bin' / 'squishserver'
config = configs.testpath.ROOT / 'squish.ini'
host = '127.0.0.1'
port = None
pid = None
def __new__(cls):
if not SquishServer.__instance:
SquishServer.__instance = super(SquishServer, cls).__new__(cls)
return SquishServer.__instance
@classmethod
def start(cls):
cls.port = local_system.find_free_port(configs.squish.SERVER_PORT, 100)
LOG.info('Starting Squish Server on port: %d', cls.port)
cmd = [
str(cls.path),
'--verbose',
f'--configfile={cls.config}',
f'--host={cls.host}',
f'--port={cls.port}',
]
with open(configs.SQUISH_LOG_FILE, "ab") as log:
cls.pid = local_system.execute(cmd, stderr=log, stdout=log)
@classmethod
def stop(cls):
if cls.pid is None:
return
LOG.info('Stopping Squish Server with PID: %d', cls.pid)
local_system.kill_process_with_retries(cls.pid)
cls.pid = None
cls.port = None
@classmethod
def wait(cls, timeout: int = 1, retries: int = 10):
LOG.info('Waiting for Squish server port %s:%d...', cls.host, cls.port)
wait_for_port(cls.host, cls.port, timeout, retries)
LOG.info('Squish server port available!')
# https://doc-snapshots.qt.io/squish/cli-squishserver.html
@classmethod
def configuring(cls, action: str, options: typing.Union[int, str, list]):
LOG.info('Configuring Squish server config: %s', cls.config)
cmd = [
str(cls.path),
f'--configfile={cls.config}',
f'--config={action}',
] + options
with open(configs.SQUISH_LOG_FILE, "ab") as log:
rval = local_system.run(cmd, stdout=log, stderr=log)
@classmethod
def add_attachable_aut(cls, aut_id: str, port: int):
cls.configuring('addAttachableAUT', [aut_id, f'localhost:{port}'])

View File

@ -0,0 +1,17 @@
import time
import configs.timeouts
import driver
def waitFor(condition, timeout_msec: int = configs.timeouts.UI_LOAD_TIMEOUT_MSEC) -> bool:
started_at = time.monotonic()
while not condition():
time.sleep(1)
if time.monotonic() - started_at > timeout_msec / 1000:
return False
return True
def isFrozen(timeout_msec):
return driver.currentApplicationContext().isFrozen(timeout_msec)

View File

@ -0,0 +1,52 @@
import squish
import toplevelwindow
import configs
def maximize(object_name):
def _maximize() -> bool:
try:
toplevelwindow.ToplevelWindow(squish.waitForObject(object_name)).maximize()
return True
except RuntimeError:
return False
return squish.waitFor(lambda: _maximize(), configs.timeouts.UI_LOAD_TIMEOUT_MSEC)
def minimize(object_name):
def _minimize() -> bool:
try:
toplevelwindow.ToplevelWindow(squish.waitForObject(object_name)).minimize()
return True
except RuntimeError:
return False
return squish.waitFor(lambda: _minimize(), configs.timeouts.UI_LOAD_TIMEOUT_MSEC)
def set_focus(object_name):
def _set_focus() -> bool:
try:
toplevelwindow.ToplevelWindow(squish.waitForObject(object_name)).setFocus()
return True
except RuntimeError:
return False
return squish.waitFor(lambda: _set_focus(), configs.timeouts.UI_LOAD_TIMEOUT_MSEC)
def on_top_level(object_name):
def _on_top() -> bool:
try:
toplevelwindow.ToplevelWindow(squish.waitForObject(object_name)).setForeground()
return True
except RuntimeError:
return False
return squish.waitFor(lambda: _on_top(), configs.timeouts.UI_LOAD_TIMEOUT_MSEC)
def close(object_name):
squish.sendEvent("QCloseEvent", squish.waitForObject(object_name))

0
test/e2e/ext/__init__.py Normal file
View File

View File

@ -0,0 +1,22 @@
[
{
"name": "Skipped tests",
"matchedStatuses": ["skipped"]
},
{
"name": "Lookup errors",
"messageRegex": ".*LookupError.*"
},
{
"name": "Lost connection",
"messageRegex": ".*Lost connection to AUT.*"
},
{
"name": "Connection refused",
"messageRegex": ".*connection to AUT refused.*"
},
{
"name": "Assertion errors",
"messageRegex": ".*AssertionError.*"
}
]

View File

@ -0,0 +1,2 @@
os_platform = linux
python_version = Python 3.10

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 KiB

Binary file not shown.

View File

@ -0,0 +1 @@
abc5e7b79c460d18d465d9865b45ad851d9d705c5cbdc3bf4e39bcf108c54a13

View File

@ -0,0 +1 @@
MANIFEST-000000

View File

@ -0,0 +1,8 @@
=============== Mar 27, 2024 (+03) ===============
12:46:16.766171 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
12:46:16.778995 db@open opening
12:46:16.780441 version@stat F·[] S·0B[] Sc·[]
12:46:16.784239 db@janitor F·2 G·0
12:46:16.784254 db@open done T·5.241125ms
13:11:42.095155 db@close closing
13:11:42.095940 db@close done T·897.417µs

View File

@ -0,0 +1 @@
MANIFEST-000000

View File

@ -0,0 +1,8 @@
=============== Mar 27, 2024 (+03) ===============
12:46:16.575560 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
12:46:16.589620 db@open opening
12:46:16.590228 version@stat F·[] S·0B[] Sc·[]
12:46:16.594453 db@janitor F·2 G·0
12:46:16.594614 db@open done T·4.981291ms
13:11:42.096575 db@close closing
13:11:42.096725 db@close done T·152.167µs

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,5 @@
{meta {:name "Gloves Off!"
:author "BLANK"
:thumbnail "e30101701220da875bea9be16e26b6a94ddc17282db7d182e0b9071c21bbc08577c99670cea2"
:preview "e301017012205227cae4e3678084fe36a1fffdd570bb676a7931924f60798ad1cd6d8c052a8f"
:stickers [{:hash "e301017012208ef176695b470783c4ae41693bdae673b2f8e28176216564d3af7b30f285817d"}{:hash "e30101701220c4e1053672d055e7614cbdcfc176256f2e2fa1d98a0cefe574f8cead03ee2722"}{:hash "e3010170122084f7535d14ce0ba5887615cd6fc135555a936cd549963cbf7222e23d35f75ace"}{:hash "e301017012205e9601ad838858f600f0aaca35615cba180d457b4833a26de79583dae9589e65"}{:hash "e301017012209e13be35abd1d83ad460c7d190eb1d94c13c081a8cd7b183ba001db58ccd1154"}{:hash "e301017012200460ab75ced426e48fd2fcf43d619268aa0effbd65ce84a4e03dee3f89fa85fd"}{:hash "e30101701220cb4a558971a4200e5b5f3ea67e822264d57c113e46fc44367b72f50cc01203c7"}{:hash "e301017012208535e0ff656c5a2dd0093aacd745b718e4315f2731f35de1f10fdd8ada1db27b"}{:hash "e30101701220c55bb90fe9e7f56f0927f4215048c4ff356eca744fdd435cc6fbb3e346a7fed4"}{:hash "e30101701220525bf59df45390098a4b38d365bad872580eb22ce290c24e13080e07ca2147b1"}{:hash "e30101701220e1052919f334506d398219ab658580b585d4ae824b0cc9ecf3aba0d896668444"}{:hash "e301017012209e17bcf843e2deb433d5d4eff9655f41cc1b7b2972c80655fe600f5e57b2a76d"}{:hash "e30101701220dfc066b094f3a76e5c71d951c8f8325a4449ed712de0a2bd171985377039a52b"}{:hash "e3010170122003e19ed9fe44e5b19d6b825b141161b756529a7821c7dc2505365e68dd6f2e8f"}{:hash "e30101701220be65a820bee00873aed5dcbe6434beccc91c10d0370f2d6b73871feb492eeb5a"}{:hash "e301017012205dca7ba03997692672fc4a9d4c6363859fa66f5b59a4d5459fa53cd2d2f5a048"}{:hash "e30101701220d835cf64a25587af237e9f4d429b00cb3ce3e6c1034f6eaeac2a0dcb9c6d9919"}{:hash "e301017012207baa0f8212ede4b59913e53070a74945b90ea9b8f3201ae391dd1b3d75d6bddb"}{:hash "e301017012205c9d1f7e11f3962076e6d202ef2ba7361ce668f9710ce09da30ac2526b0470dd"}]}}

View File

@ -0,0 +1,5 @@
{meta {:name "Flex Dapps"
:author "Flex Dapps"
:thumbnail "e30101701220873700df21773072d422b6026b21e9c92223abca3b394d569cfbdf9061123cd4"
:preview "e301017012201d145070ed2205a378dbc8a6093a2d54a7b4ba2ac8d2e2f8aba682fa06ee7138"
:stickers [{:hash "e3010170122074ae324e29e253897333273224609092d4b31f68d28f24fd5273c8ab6952d3b1"}{:hash "e301017012207d5c34ad915ddba8cea762c9b287a9705618cf6f22e3dd7eb3a43711fdeda0c5"}{:hash "e30101701220d09202c0a43e2e1b46ef750c194372441ed3abe7b24981996c2d0e03cd70c888"}{:hash "e301017012209de39eed057861d52c7f048eea091823066e17dd56d1e1bdfea5c3ea55471eb9"}{:hash "e30101701220539de8ab5bb31a14f1a01be45b25affec70071eefefe36519cced22b8cc524bb"}{:hash "e301017012206b47e20800651e5b2454597fb5bfd912733504a8b6392dd531892bd1326feb3c"}{:hash "e301017012204527a61f5725a50481fb0d8e8c790efc19486511f7d72e6fb82725e5bc60529b"}{:hash "e30101701220379255c6d42656a63c67f3bea9bd59a3aa7088d3a60515b75533d2ba8dcb5e34"}{:hash "e301017012202a7a01d770dd6bd63109b3d78ee915e21213f493639b3f7ac6272ad9bfba1808"}{:hash "e30101701220ab4d8ffa013110e163c9544f5c95f0ab3e858b4cdba1484bb39d139b58f9b116"}{:hash "e30101701220368a1ccbb28087eb8f8892958217dd31fc289866bfcc5989ac073a370dee9b98"}{:hash "e30101701220188847d73155996a7e74259e7e4daee91395696547f7b68511c079b25ea0283f"}{:hash "e3010170122010c381bbaf174ea70b51ceb1b13b5731bbea0614396fb7ecdfffd317decf701b"}{:hash "e301017012203d33e824dac0634bf3000db005e2706bbd617d715984c572c23c16a19898f7fe"}{:hash "e30101701220163e139af650225a84bd0b8028e2dc47f1ddc29fc7c817aaec021a7ce55ab306"}{:hash "e30101701220556d1403794f40495d299f3dff30e712be3dce04cd99538d440c7073dda1f23a"}{:hash "e30101701220cb6c6b567c9955c61a2dac1a349206700f4edc455facd90481a6258b1c9a981f"}]}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -0,0 +1,5 @@
{meta {:name "Geko Story"
:author "Kazwes"
:thumbnail "e30101701220be83d1048c6f21944e1fc91061387b38650649f9c02f8d8f97b3426fa86fb211"
:preview "e30101701220215cf6f423ce201e6084b4778df0e2d24688757f49f991193773d690ecd79317"
:stickers [{:hash "e30101701220bc72ef41c365f7e0e60e400e5025740ca8f82a0855861ea1d66b46246900a123"}{:hash "e30101701220603a4868b324787f4260ea2a2b1bd20b7b8ecc5962b0c3f74e49117f05a3b04b"}{:hash "e30101701220eb1f135ab6cb03beb36e097af196e4b8624bd11a8476e8ac88aa50c0732dca9f"}{:hash "e301017012200dd07d73782d9f58229d45efe784c8cff95331e1b6d210dc1e15132efdf87f3e"}{:hash "e30101701220134938d9f54f55f83358c2b8ad1e32259dfde562b5648be41df7238e70b5cbef"}{:hash "e3010170122097bcde34f4128486ddfbf7ae42c8d28a27074832bd2eed09927b98ba61f6fc32"}{:hash "e301017012202f2cc01a9f9548f53afa390bb9cbdcc4853b1045bc288e6d7f38768d83dedd49"}{:hash "e3010170122066ab31f6f1ceeb289f97b0da47197450431b37bc62e5f04806e9c2ab82e4429d"}{:hash "e301017012200351ea55cc885a90fb13b4305e6142c4c96c0d061f3c2095b776445471024a26"}{:hash "e30101701220558218d656a8d1699cd46d9eb29d12b247c0bb433a827c07b612aca97962e6e0"}{:hash "e3010170122092ac180e783922f1cd1fa82a8593f0f653c364a86b54f2931db761fa41d3192a"}{:hash "e30101701220e8d62721648fc04b05272903b4f116573d42927d015c313a18a20d2d2d375fad"}]}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -0,0 +1,5 @@
{meta {:name "HCPP20"
:author "Melvin Alvarez"
:thumbnail "e30101701220ee8d0e8869aa7e31490b18c6ad3e28a30e8dc06852595463e561c0e2acb6a8f1"
:preview "e301017012202fcfa5cddf18fccfc19be1cf5e0d52c476122dc1d6b2bb31e102fb9fe8f629a0"
:stickers [{:hash "e3010170122066ae2db8b83add4270769ffe8aece0afb79c0c29708e3f28941647268d7eb407"}{:hash "e30101701220114c763c848be25e077523216d17d3e2ccbae4a3b004179d132b7c91a2f13009"}{:hash "e30101701220daf713e4489b982338a2a823f1597edd2a0fdcf358b76375b94065ddfed50929"}{:hash "e30101701220f4aab0a4ff5a5fb2c565ab114d76dd42693302999d6392b467d6753ea9b55c12"}{:hash "e30101701220a2855135137c9c088a23658c93841f5fe47cac26983f2f19b154540665597619"}{:hash "e3010170122095912a0ff137b67465982a3fc6f8b41692e0eaeaa17ff5eaa5ed25d611c68f4f"}{:hash "e30101701220a5395eeddabb98d799031dc2bbc8bc972bf7c6f199f63003606b1c9a7348ae32"}{:hash "e30101701220fb35ff7341658353cda8d163dc979fe5844d574a3e621a761a0c9a9f163143d5"}{:hash "e301017012206328887139bd7de5148a881ae029ea75357f3e01782d01f20011ae72d7d43831"}{:hash "e30101701220e935da555834c78250fa7b2787987cc6f6db20c12dc158e63e54abd7a4cf3902"}{:hash "e3010170122058532a9d05e6352970c0c0806dd3a1ee0447866393c504713faaa0d2fd987ae8"}{:hash "e30101701220046dcd1534e099cec7ce82101ade162ab4d2cedb241f8da12fad1b7d31ddafeb"}{:hash "e30101701220277fb48b51c279960c3ff0628e726e72f9679452583aa7deb640951111c98c5a"}{:hash "e30101701220051fbd8292648d3adcefa266fe1a16713172d8364b4519f69682b55a44687eb9"}{:hash "e30101701220793880b3be9fc46eb4657bb97b5dd021ff0d8fecec3116ca48da8e6b76eb15cf"}]}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,5 @@
{meta {:name "Status Blue Fire"
:author "Status"
:thumbnail "e3010170122033099a15a215267b117fcba796aea248b6062b614a2fdae6b63d47bef808301a"
:preview "e3010170122097f821431baaf9161d5a2f83966d4f8b76a9a50d7e2119639305cdf2cfab7a79"
:stickers [{:hash "e30101701220d444419a474cc5151661bd15e26fc361bbe6590e99f565e0d0006f7bd2d82e8c"}{:hash "e301017012202a858cc945af30be5c1d31a5709cde4d36edf201e650a2d4f8714234d713d6c0"}{:hash "e30101701220d1a479a51bb40a9e2fa7911beb22f17da4a9b0067e365e55fc16b9d172b666f6"}{:hash "e30101701220092236e54bb771a241121c72fe40b6f3db8914b0a5e77d8e5ec0244a05bd3246"}{:hash "e30101701220ec585ffddd856cf2db7afe213161b3e1b88c127e8c2869ebe3ee6b57f0f6fb6a"}{:hash "e301017012202f1ba39269e9d9b9db48b39088996344222ea0868a1d67a6637ec05a4186c6d6"}{:hash "e30101701220655aecc888fa92b4236bb67e27001a79a43c65f7f9b636290941c28afef58e81"}{:hash "e30101701220c8afcdf82946796d7ec854b0a3202df6332712eca372adb9783d790a356dc98a"}{:hash "e30101701220f9021715c3308492cca0c314a138bca4773b432500639c2de4aa6823e12f378e"}{:hash "e301017012209bb202ddbd81c28a3aa7dbb2f6eb96fbe76c30d6dab1eb1b6c483bae7c66900e"}{:hash "e301017012209df6712fffc07507183ca222c0b60b3e002cef901dfa1b9a9d02a71886641f02"}{:hash "e301017012208231695c75e893f485816fe802a4827056dad86283708127dc4400893740a6ca"}{:hash "e30101701220bcc65ddff08c287fd115512d1833e6f19efbf3f560c7e74f9ef18cbd456dc058"}{:hash "e30101701220b1d0a8057beecfe64ef1f34208c26a2a731391fd65768121b94a456e69c916bc"}{:hash "e30101701220bd28c22baf338a3f0c3391ad7b61b726e11e1fd0b3373428e559fcbc50efd553"}{:hash "e30101701220aaa950f7516cca7bcd6d0d6bdb0485b0bae5863bb948ba13b6df4fe7d1fa9e4a"}{:hash "e30101701220ae1bf86ff5c9edeb708c3bcc76374ce23deb9e99f75f20d9442335fbd8a6705b"}{:hash "e301017012206da1a9e1b41e2fd8284f73e97516bbff037e8d81893c95d7c70871ac8a178c99"}{:hash "e3010170122003d91734ccf45195fb2a9e06ee06f718ea63720d7167e631936d75c4a59f2ccf"}{:hash "e301017012201bc272f8c809d7c318e239f27cdb2257266ba964d91d9751d7a5f23f831f4451"}{:hash "e30101701220c1b010073c69d3630c9969356fec55bf2d42c4d2efc51f0a56bd42d4a367691e"}]}}

View File

@ -0,0 +1,5 @@
{meta {:name "MyCrypto Taylor"
:author "MyCrypto"
:thumbnail "e30101701220fe0b90aa704e2c0d01a3319ee82052aa04867937edaba0a4c921bf068379489f"
:preview "e3010170122079f39b2454fa44968c5cfcf2523116a6bf6e4d78eb0b54beea7f9e15414cda3b"
:stickers [{:hash "e30101701220a3c3fdee0f02fdce4f43bc19f98ae5ff63f52370ccb7eccc915c031e7148d701"}{:hash "e301017012202eab276e6dda5a68173d25a523e3d77c0a47b3d31a9fa6a74f18e0347a52d630"}{:hash "e301017012204a403f4465897868b3f95d11b9dd27b78dfb34ed252446a22f4943a1e10b7820"}{:hash "e30101701220f9e4aef4ceb0b64c05e45980a1c34114ad8a0409b63324b7d119efeb5e00da9f"}{:hash "e3010170122005ab729ca894c0e95b2a11f9a67ee97234aef88084c7fa91586682f8eb4e75db"}]}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -0,0 +1,5 @@
{meta {:name "Infor the Techicorn"
:author "Brooklyn Design Factory"
:thumbnail "e30101701220965e78f8cf56c81470567ffcdbba6ed7102844db1226c5787fa169061cba8265"
:preview "e301017012206510ff8e1d37b8e1a7f812e6436ef6dfda266c8921bcb3679bc63b1d3f4537ae"
:stickers [{:hash "e30101701220531612eae8a2f99c0c8aca5f2c45c79a1e6b8340332d11fdefd9070c3a6f3b32"}{:hash "e301017012202524a14d6616cd6e80cd00bbd23a3a92e0b1ae820972b5387ad2f35cd8d4b036"}{:hash "e301017012202e5e03c7f1d0d3dae5d732bcbde4bdacfa2981ce0be9e51f688758b115dae60f"}{:hash "e3010170122099e65bc11a30d1900bc2d794f0fc2e2498d26f6d8b9cd1ba746219a70850a81a"}{:hash "e301017012208658ab4de74e2ea492a3a552ed86eacb60fd10592aa2a2480bc130964f5928b4"}{:hash "e30101701220e6bdb2ef306e99ba76ddce2a3382ebb0756d7973bb6807ae1f379dd03ec80e1c"}{:hash "e301017012204426c52e586dee222bb1cde8a61665b6546c7f9411ebc42aaf57c54fe3f62304"}{:hash "e3010170122001e8d85d89265c214f2481558709d050bb33e85ff7779b9650ef3a242fb9791d"}{:hash "e30101701220a253b011ba109aeccfe22ac8ea8f1366bb52fb1e3c625432202b2db837a1af1b"}{:hash "e3010170122056c9e9177acf8b0dd7d3bd12f378a35bb3d3409d3acdea24d56711b21086d73c"}{:hash "e30101701220c123bcb5f00418b589a4983b6e3f3654713c3a3ce2147c8211ec23993f0b4ce6"}{:hash "e30101701220b5044589537660ab7a7b2797278c4b2997c70eb6421fb9a83fb9aee6fb54bb98"}{:hash "e30101701220773073c867c8286916f78ef26028dea170053ab2a7382a1091bbc418bd575c45"}{:hash "e30101701220810be6f9dd3bf02a1f01746c0298c3c82555d6f227b5bf0392d394032e9a2501"}{:hash "e30101701220503df87f9ee2f5b6baaf3de76e28d775f4c9fc350235aece3cc66f18bc5742bf"}{:hash "e301017012208436337934dde644437c2075df74af6e71826509c9fbdc8b600c1b6052a2127a"}]}}

View File

@ -0,0 +1,5 @@
{meta {:name "Meowy Christmas"
:author "Mindful Design Lab"
:thumbnail "e30101701220fe33097fb023b2c8909b34296c399569321998e1b027ef6e39d47addeaac1d0a"
:preview "e30101701220dfa41d3b1787b7a2a741d5514a0820ff2b71fedcd09c021ee74cdc8dafb629c5"
:stickers [{:hash "e30101701220f6af4bf36e4295faa112c881e83ceeacc408dba71be326ecffd1b16c44b24899"}{:hash "e3010170122022cd017c0592de84aa4dc3659ba9463e574878fa69ef5d7518527f812e2dd9c8"}{:hash "e30101701220d1c1eba2269a27f204af9f9271a0c1d97dc861e06e9d73fcfe6988a7174c964f"}{:hash "e3010170122007d2fd8dbd76ebac7aa76f0bb5b8ce7f71a9fe8ae604f5265413f3c947cfe1c8"}{:hash "e30101701220c49f8d56d68d69052471132cd2ffdce6840b3cd5450b40905334e030a9beba70"}{:hash "e30101701220def85dae79494d0a2eb1b38b546c714e667d3d89714a1922efa8629a74e9f90b"}{:hash "e301017012207df83bdf02ba831d9dd68d079f6494fbabf86d98e9faef4cfe54cacf41f6cae3"}{:hash "e30101701220501341816ecf63a2a9d7337edb8f3090b2a694ef9771d4a49f0c4355e241ca3e"}{:hash "e30101701220fc5401c6d555fbe8fae85e4480620102bbff71afcc2234c06e52e9774293a21a"}{:hash "e30101701220d89b192fbe75648c66aa8f12fbe3164ec57363ccef829cb7ca8622dfd7f2d8ad"}{:hash "e30101701220d2ede7d91e85bc60ca46cd07728df8c0ac4ebb0350f3354b294e8bec69c8dba5"}{:hash "e3010170122029f662ff7ac94c2fcd057a786dddbd3b0a08584560d42855fbcbfab6d06f1db5"}{:hash "e30101701220a4650b468a367efbb7467537a1033285bccc5ba45c2134f5c1909ff588fc3441"}{:hash "e3010170122002c90b29e70bc935c4af8ab560e93a33ac4ff11ccb705095c5f76f2257d10c6f"}{:hash "e30101701220756641b638ac7c66c0ed615307e2312a956e47b65c04b782b61f672babe02eea"}]}}

View File

@ -0,0 +1,5 @@
{meta {:name "Tozemoon"
:author "David Lanham"
:thumbnail "e301017012209c5ba15fcb860b0715190d342dc94bd397692dc318b1b0c72f21e912904bcb93"
:preview "e30101701220d0e9e47e06047b0bb26213ccde4bc7836199c91b3a3ab0f3fa20ac54f6e5691d"
:stickers [{:hash "e3010170122093c41fec87274b48d07c067f478e20c132940bad57712b3215c5aba9f1862eea"}{:hash "e301017012209af36e3307f85f7c3bd741ebede2c8a41d12cd0f2b95de3c31df0a76f8783c49"}{:hash "e30101701220feb1e9c32b93efee4aa73b9f42553b789bd3eb561b0972d5841a6f16f603e89b"}{:hash "e3010170122080c27fe972a95dbb4b0ead029d2c73d18610e849fac197e91068a918755e21b2"}{:hash "e301017012203d5f53a4700324ca36d0b7dc4ab8c292be030c22578b7c9e2a199c887f35585a"}{:hash "e30101701220ffca78bc997187d454db11edc9bd4d3f1d5b53ec42a037ba39d773214cd3fc67"}{:hash "e30101701220b35bd4f43db76071ec893e1072028cdd86c72068abf5c7d2e5ff96bb8644c8c7"}{:hash "e3010170122001bbe2f5bfba0305a3bdc2047fddc47ee595a591bdee61de6040ffc2456624e1"}{:hash "e30101701220d5c71b72162a802b3cb2e3c61758d8a9780d48d78c467d160c52adc7e1e35a92"}{:hash "e301017012205225562d0790974887dabdf9d257cd808d1e470f0ab41226e1be2d522f4639af"}{:hash "e301017012206d9689241f5504021068e3c23befc859384a0e9c00c27108b76e573968f61f8c"}{:hash "e30101701220ad202b9109dd82f11782044ec443dbd56ddf16270c353704505da03f7761aabc"}{:hash "e30101701220ad95684efa3e42d4f82043210a8946745b0dd0a9b58d8f6902315047a9ee922f"}{:hash "e30101701220b91b449ec56ade3ca73fd51ac76844280cf58c01a91806e2f0417d27f55254aa"}{:hash "e3010170122036fa754dd890cbcdd66666bdc4ad18d2ce5284c8f6b6ffe2c0a091eeeba1951e"}{:hash "e30101701220b06837823a598d52584f5039882413b3616299bf32ea162c6339ada84138adc4"}{:hash "e301017012205b9b17d402f3b97fc9f43a1d389664622587b4cc109dd9fdd5ee2e8ab5ca1b7a"}{:hash "e3010170122054c748ceea79aeeb79b83bc940310ec855cab03857d60b575c4fa560572a0f11"}{:hash "e301017012205f0d53af63c96e3d778dbb47f2c57fcd417567f4c97754531f9b914cacba5e70"}{:hash "e30101701220d345e5223c5dd31c28e0d65088848e76a6144ba24c3c779d71b068f342345190"}]}}

View File

@ -0,0 +1,5 @@
{meta {:name "Salad Bowl"
:author "Brooklyn Design Factory"
:thumbnail "e30101701220f6a334d2712ef24c9b770a753a3e0c61aa35e15e485d72a7581e5fde4b4e3683"
:preview "e30101701220ad9340c85d3ffc274eb72847f932be00f950b6ebf0062be19bba1d65a91acb93"
:stickers [{:hash "e301017012204ae736b13b5d36c889a715687603133af304fdc03820eacf85581354d98a6712"}{:hash "e30101701220ebd80c7cb8307d64336fb9514687b71b6668ea34d46b04d6a59f4a598b5881d0"}{:hash "e301017012204d2e87e21fb8d3718f4d6c8423e98e9112544551985df9f83a4e465a701a4b48"}{:hash "e301017012203271dfc8927c3cd27c01380a6bc250faef7ce2c68c95262e031a29fa83e50cf4"}{:hash "e30101701220b4847927edbafee3f577fbc286896db789538138dc9175ec0a19036e8835756d"}{:hash "e30101701220e71c9221e01c000d8ba1043713ad1c0cdefcce49af3f1d96dddac1f054919fb6"}{:hash "e301017012203b3aa17f2aaf855a49ed56e31919f7f7ae8d053d2911aedba83e591e33b82434"}{:hash "e3010170122072c5e9dcf6b4f7adf6088158a990ccf274fb8b9b7e507038fba096b46788913b"}{:hash "e301017012202179e572ff632085b448dfe2887a36a83e495acd6fe0307a1f6668ca0709b077"}{:hash "e3010170122032463d79738d12cb02bbb0023eb16435b94b4be3490e6ddbd6118474c534b826"}{:hash "e30101701220516b38ac2eb7b680a68cde8f8e7c6d330a993a8425fb623117cacba6a3fdb8e7"}{:hash "e30101701220c0944d1daad05018198eec8f496228edad71b952953c37a5362165c2235a82d5"}{:hash "e3010170122057b680d8a46ae6435c11633e4ca7ec83f411c9863c49b6069511f4652aa5f76c"}{:hash "e30101701220d2a41d7bfa31541a65a079189114bcd63c5d21d24b33af82fbc2f3d553136f32"}{:hash "e301017012209d670fdbfb075f9e1ef27746e7a1e50386b3e246d860ef6e6c478fa1a98c97f2"}{:hash "e30101701220a0df81beaf80ba987896e03eec223fcbc5599f37daf6e60388e223cf2b93f40a"}{:hash "e3010170122091c446ed54c55c6edf7b64a1d6b5dfd92d1c95472b135e05e31a704344128def"}{:hash "e30101701220999c3d750efe949a82eeedc17165fdb8e960e13d0d2e6e4315bae3b3eed04538"}{:hash "e30101701220bed5966ef10352d9f9e0746e376884d1602dbca02a6d3fc1ef852516d1b03163"}{:hash "e30101701220a5b5f70d5ac5d0203da5146b87f278d2c88aa39d9b5ff959ef00e512b324bec5"}{:hash "e30101701220f539e23985ac3acac097a81c0ba775a6b0220df767b3844a8502f9b49902cb7f"}{:hash "e3010170122015498aed30b352f34f3900b5b6d3b303ccef50b49ceccfada8e01cf165626e0e"}{:hash "e30101701220eb4dffb576e0d82b1ed1c34147a2d9ae395c1e104462f3263738095a7d133189"}{:hash "e301017012202b3b9e0d22b4051a3b5fd3405bf03594eb64d0c2e990eee4328a13cca2e4a21d"}]}}

View File

@ -0,0 +1,5 @@
{meta {:name "Kyber Network"
:author "Kyber Network"
:thumbnail "e30101701220f772002a1c8fad7af149999d8aa943b37841bb1ae1465bc389357293f18ec261"
:preview "e3010170122029a9705f914645b8cf9754541633fe4a38832ab650868f9254ba8ad11e8fa4fe"
:stickers [{:hash "e3010170122072d18671b24b34a84e0851fc0477f86a37b27c2bf181fb04e561367cce87512f"}{:hash "e30101701220218d9c4f1d83974d00de9bf9328c5f479c559fb05abf9c8f7c7d674ebbf89673"}{:hash "e30101701220e6668dca7359cc780a3fa32d5a5c3cf02333a0cf8d807dd4b7fa221e0c0562b3"}{:hash "e301017012203be79845b37acf389aa174fdd83ce8c9e05c587b23daf74d6e8bf926aee67a38"}{:hash "e301017012204a2c7fd3ec2813101cf427f7d112bf5f93e0170a2358431461f25d7d3c307d62"}{:hash "e301017012203aab35d88aa1574b5078a0338b2545cddc4ee681455b38ae476f2fb587bb773c"}{:hash "e3010170122045c295700775c477fc41e2353b13185ea83ccc54246d9b4ca9b6b9d6ecc891bf"}{:hash "e30101701220b6542c321fd81495fedb926414ba56a6a70a6a8ac6493103f510ae84222f5dbb"}{:hash "e301017012204e2a3154027e1b6d54b7a6dcedfaa4549dfd7b67f99374d1c98718005a8c63a0"}{:hash "e3010170122081112d517d9c64d43074b02603ac8f32a174007f3d9448946048800f84b5ff97"}{:hash "e301017012207d2ddab443622a4f2512e9941436c8fa51639f59df8b01176713e745467d79d6"}{:hash "e3010170122058a803c65450ac31f4ae580c972a4be24882da6377435614ff7223601c034f7f"}{:hash "e30101701220a51ecab50da228e80ddf56be6cf5f514f411f9c58c609105a7ec5213b022a59c"}{:hash "e301017012206738ac4aef625648876668f6c40d59c73a17a60c9830befef12c15a4413fc180"}{:hash "e30101701220aeaf84ba7712c440b41dfe957090f8ca592279c7042c08d3287319121c4c0aff"}{:hash "e3010170122084da9a758b1583e869baa0762b6c533c0be92265cc38def2f5714a7b1847ec1d"}{:hash "e30101701220c301c4aaec50dbabc2ed47886e411d86519499b6384b86714030db68648b194f"}{:hash "e30101701220f33e11e981e287076136e471bb8943c98f42111d05867fd0e0c7b9731cb170b4"}{:hash "e30101701220f8e368e0ad5ae5148e5a6c3c3dd818c534e9523de9976cc09b06974b0295919d"}{:hash "e30101701220293f49b119ae36a77290e9a430fa2f1207cdd2530f6603edc01d4f5b2e49fd0a"}{:hash "e30101701220d08c83e029db66af708173ee3bb4909ea2553e2db0f3246edb3fef1fc2f65968"}{:hash "e3010170122069a75205ceb3f2866e831ba278ce87e9a6a42b4f6fbbdbfda87d78fc6db34021"}]}}

View File

@ -0,0 +1,5 @@
{meta {:name "Status Cat"
:author "cryptoworld1373"
:thumbnail "e30101701220e9876531554a7cb4f20d7ebbf9daef2253e6734ad9c96ba288586a9b88bef491"
:preview "e3010170122050efc0a3e661339f31e1e44b3d15a1bf4e501c965a0523f57b701667fa90ccca"
:stickers [{:hash "e30101701220eab9a8ef4eac6c3e5836a3768d8e04935c10c67d9a700436a0e53199e9b64d29"}{:hash "e30101701220c8f28aebe4dbbcee896d1cdff89ceeaceaf9f837df55c79125388f954ee5f1fe"}{:hash "e301017012204861f93e29dd8e7cf6699135c7b13af1bce8ceeaa1d9959ab8592aa20f05d15f"}{:hash "e301017012203ffa57a51cceaf2ce040852de3b300d395d5ba4d70e08ba993f93a25a387e3a9"}{:hash "e301017012204f2674db0bc7f7cfc0382d1d7f79b4ff73c41f5c487ef4c3bb3f3a4cf3f87d70"}{:hash "e30101701220e8d4d8b9fb5f805add2f63c1cb5c891e60f9929fc404e3bb725aa81628b97b5f"}{:hash "e301017012206fdad56fe7a2facb02dabe8294f3ac051443fcc52d67c2fbd8615eb72f9d74bd"}{:hash "e30101701220a691193cf0559905c10a3c5affb9855d730eae05509d503d71327e6c820aaf98"}{:hash "e30101701220d8004af925f8e85b4e24813eaa5ef943fa6a0c76035491b64fbd2e632a5cc2fd"}{:hash "e3010170122049f7bc650615568f14ee1cfa9ceaf89bfbc4745035479a7d8edee9b4465e64de"}{:hash "e301017012201915dc0faad8e6783aca084a854c03553450efdabf977d57b4f22f73d5c53b50"}{:hash "e301017012200b9fb71a129048c2a569433efc8e4d9155c54d598538be7f65ea26f665be1e84"}{:hash "e30101701220d37944e3fb05213d45416fa634cf9e10ec1f43d3bf72c4eb3062ae6cc4ed9b08"}{:hash "e3010170122059390dca66ba8713a9c323925bf768612f7dd16298c13a07a6b47cb5af4236e6"}{:hash "e30101701220daaf88ace8a3356559be5d6912d5d442916e3cc92664954526c9815d693dc32b"}{:hash "e301017012203ae30594fdf56d7bfd686cef1a45c201024e9c10a792722ef07ba968c83c064d"}{:hash "e3010170122016e5eba0bbd32fc1ff17d80d1247fc67432705cd85731458b52febb84fdd6408"}{:hash "e3010170122014fe2c2186cbf9d15ff61e04054fd6b0a5dbd7f365a1807f6f3d3d3e93e50875"}{:hash "e30101701220f23a7dad3ea7ad3f3553a98fb305148d285e4ebf66b427d85a2340f66d51da94"}{:hash "e3010170122047a637c6af02904a8ae702ec74b3df5fd8914df6fb11c99446a36d890beeb7ee"}{:hash "e30101701220776f1ff89f6196ae68414545f6c6a5314c35eee7406cb8591d607a2b0533cc86"}]}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,5 @@
{meta {:name "Status x Mox"
:author "Moxarra"
:thumbnail "e30101701220a156d5c7ed6a93a986432390d0e76164d96aeecad91c86ec5e7ca60c15fd91ad"
:preview "e301017012209a30d94e6fd902cbf241dca23adf4ec776a7ab93abdec055f840b09455fd08ce"
:stickers [{:hash "e30101701220621944ee8fc00e006eb44b4ea2e79e05667867dd15bb72667fc2da2f7c6dd44e"}{:hash "e30101701220fd6f82006eaea295a027ad7e1fb333297876c050a119f694fe6663c393f95e08"}{:hash "e30101701220fb8171894740049965f872b841b88c337ca75100ced7cfb58bf40b130780353d"}{:hash "e301017012207f71b6b9d2016003792b809f70b8f9ad06dc1bd54bfbef172b183e900989d253"}{:hash "e3010170122002f92aa870c4054e1c09996e4c7ec2a18ca3868cd53b653685b40e6f420782b2"}{:hash "e3010170122049b3b48f4b35b11f2f4acf5d77e1291728551d61a92db0bf1870a20a4ec82164"}{:hash "e30101701220d20ff59a18df19a6a4565b877fc2126251c2c4f960c0dc47bebdf82e2bac9035"}{:hash "e3010170122016dbb039bf1107d3203c47cb31ad8cb5c31fd81951e20f9b31c73dddd666efc3"}{:hash "e3010170122040df0288ee1e4696e256949ab1d9bebf0ebe820e8df6401c42453a8465146370"}{:hash "e301017012204586040200dcfd914b642ad5ded8436a59969045682b7e96b6d0ab9d19274aaf"}]}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,5 @@
{meta {:name "Foam Fingers"
:author "Billy Osborne"
:thumbnail "e30101701220f4e6651aa43c56c11d2fdc1c8bee85e5f7e942a1a86bf6f2a955ec703fba32a4"
:preview "e3010170122015adad810f81450639b25fb93a945ddf9121a2bddba3c8d217f3ad28581ca560"
:stickers [{:hash "e3010170122011341156592573b17306173544693b801c7158416dc66eab92d377d652b812e7"}{:hash "e301017012208c360945c22c858ceadbbd48c6e6c294406df7924dff9f4699711f9e68438e99"}{:hash "e301017012206a6123454f2c2a60cc99bd35f1b2f23ce53b463747567942242b0fc8034a9aae"}{:hash "e301017012203edd5d845830143654ff157d255e7e192b38e7385a168994af5150369846def3"}{:hash "e30101701220f4deca7b131d6519cd1b84cb68af67ae2fd124e997109e202767013898fd41f0"}{:hash "e301017012209514fe9fed40e5356ec27fde37a83b211be252ca1c007826c509b36a2f35b9d9"}{:hash "e30101701220e38e9388949745d2dc7b5cab674490cd6995c1c8f49a89426a9e59b449a40e08"}{:hash "e301017012202201b44bb6435581cbd19ee5100cfa6fa947502e48564d1778e11454a26c8a35"}{:hash "e301017012208fa9755e354fe7e9f7790a7eca51bcdf111ae019b3273893cfa6f6773f954cca"}{:hash "e3010170122076a91f4273040de42e45ab622a52d5721be6b24e501c87af1b82fec712f40248"}{:hash "e30101701220911b38061a8eb46b60a50feba033b81c4707cfd3a8b062778dd25521d108a132"}{:hash "e30101701220050dfacc443391966c7de52d8e329823eab5653e48828945d7a17d727e6291bf"}]}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -0,0 +1,5 @@
{meta {:name "ETHDenver Bufficorn"
:author "ETHDenver"
:thumbnail "e30101701220d06f13f3de8da081ef2a1bc36ffa283c1bfe093bf45bc0332a6d748196e8ce16"
:preview "e30101701220a62487ef23b1bbdc2bf39583bb4259bda032450ac90d199eec8b0b74fe8de580"
:stickers [{:hash "e301017012209cba61faaa78931ddee49461ab1301a88a034f077ced9de6351126470b80fe32"}{:hash "e30101701220e8109e8f4bf398a252bfb8d88fe44554c502c5ab8025e7e1abba692028333a97"}{:hash "e30101701220079952b304013fe6bcd5ac00a7480e467badece24a19b00ddea64635d9d5ecae"}{:hash "e301017012200a21dc7c6ef96cdb3f9b53ed1fdea4ee22baa8264a208c3705a8836b664efd8d"}{:hash "e30101701220b3ab61589851545f2c3ce031beafbf808070c36e6abbaf8c7c8076458c3f06e0"}{:hash "e30101701220a5274e0415793e1a81347f594715d6006538d78819a731324d3fec19ab762deb"}{:hash "e3010170122076ae7e71383586ecfa0a6f9cc8af80e614ae466facabe48bee167d2921dd0420"}{:hash "e30101701220bfe0dd7e214eac7cb75c7e8f6e9b1d02732e01ffc67a460cf3b1311aed92a2e9"}{:hash "e3010170122042aa1a5e8dc25072ee6d0b43535701fcb44c58b18e6c14a4956a3212f64cf236"}{:hash "e30101701220107d2ca1901cd8ac0ce54cbf93f5ac86d931c5d4bb16d402315a2a8197dac371"}{:hash "e301017012206d2d66f7f2fff9f366e910f4d157f7ac59dead3e42b2f9fbe5a9b95aea146d10"}{:hash "e301017012203a86612e82ea0db8248875e7993e73a65ee263f6bbeb2b1738eb12a6ec572965"}{:hash "e301017012205bc05fe6517cc00e95e34ba978dd2a5cee91e4dc4efceb4505fecc38887bb7fb"}{:hash "e301017012203cea2a96032284de80587e994c669a42405d49ce599281866ccc14f475e7870b"}]}}

View File

@ -0,0 +1,5 @@
{meta {:name "Shiba"
:author "Mugen Feng"
:thumbnail "e3010170122053ca0e6594e5e11a0b28b2ede71c26409f996fad419010c20d075f4452463300"
:preview "e3010170122089927199d0149f15a62f3005e531c2cc1a71f145c782a72e393fe4e3a1bbe23f"
:stickers [{:hash "e30101701220cb70a410e0921d68c9fe2f7916a599fe4699f01b8a1b303c33d153117bde12dd"}{:hash "e30101701220c236c51526c0c7d8f53545a35ded32af9f70f78966abf8b4b8e378e4093418a9"}{:hash "e301017012202ae8876381cdae1f165fefef802d8dd9bb32b5e981e90398a01b4620e9261b57"}{:hash "e30101701220b5b71aedbb89fe32bd0090545ebe5c8665c9e153883b57bbd34411863831b3d6"}{:hash "e301017012205f371987eb222cc08918f45ccf94452de79af6bae57bbe027253090f8a849b66"}{:hash "e30101701220c6c45a2058a34b0a3e2728da3b4a68c06fb91de11e511fb09934879318816390"}{:hash "e30101701220ec871a23a328d72c1da2f8e2bfff18f956ca054524c1b97476e0cf27239d31fd"}{:hash "e30101701220b4300cccb984e893e5359f1c426a0d2c4c00407a50475dad9046818726a74168"}{:hash "e301017012207c7c90b29e1200435f6753e3e9f62b5f18198403c060ae4448b25151a0772a54"}{:hash "e3010170122044d99cfab8a61d3f3f6fa111edc7fa81cfd3a6931e2b104c5e3194d4500f7867"}{:hash "e301017012205731049b7d31524573e83b07ebdc262278d876737623a60dba3557d9bdb90867"}{:hash "e301017012209154ef3dd24e3a43167388c1b2f287b93d32f9cce0c87e07348e7308e5512873"}]}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -0,0 +1,5 @@
{meta {:name "Stanny"
:author "DesignBlock"
:thumbnail "e30101701220602163b4f56c747333f43775fdcbe4e62d6a3e147b22aaf6097ce0143a6b2373"
:preview "e30101701220ef54a5354b78ef82e542bd468f58804de71c8ec268da7968a1422909357f2456"
:stickers [{:hash "e301017012207737b75367b8068e5bdd027d7b71a25138c83e155d1f0c9bc5c48ff158724495"}{:hash "e301017012201a9cdea03f27cda1aede7315f79579e160c7b2b6a2eb51a66e47a96f47fe5284"}{:hash "e301017012203d9f7e1a537d4cbec0a4b6b97c542443de1b4c2bd8cd72fcf7889e32581e65c6"}{:hash "e30101701220bef02caced8a4df22030b1781983d19626af66d9f22a951411257bd1cd3f0b33"}{:hash "e3010170122056d1eabeddcbc6e096ce9ff8166dbf0b9b9cdd07e97192b604feb89a22f2a9c4"}{:hash "e30101701220a8cb76e481f3633cf41191f39e8d377a33ec73208d954085f5f3d401054dd351"}{:hash "e301017012205c4fa130b5be0286cbace972bcaf6c6546e22e33be4452e9d36458ed73bcd2c9"}{:hash "e3010170122033c8afe6b6d3f383b8ec77413c31572c6c60f1d788789c9080f180c8aed326a1"}{:hash "e30101701220449bb60c5212998907efa31a93ecbcc0b7870e510735d52f583a9081bbcaf111"}{:hash "e301017012206aa473f11959bf74ca254a0513a008f2f8cb392544bc8a607f7703d83ec43b81"}{:hash "e30101701220c34460dd958fa7e3ec5ba8a8328e49c49f1b44925caedcdb223b709c489f2764"}{:hash "e3010170122088ae75184d06df2768b3e828fe7740ba87267669a4b3c1b1e87b14948347dffe"}{:hash "e30101701220932b074a2e176869827206f9b7c5426f2908c8a306466b71cbdf02ae45ab4673"}{:hash "e301017012207b5a19c6883c33bd34711255989a8ce65ef273e7410d733b9a15859a8a78dcb9"}{:hash "e301017012203af82445123a4d807bd89254e9e4c85839bc5a445b261d61025fbadcc8f0a2f1"}{:hash "e30101701220c958788be30288a7614662a0d082a27400bb0df67308e8b734bfd9cc2d717c53"}{:hash "e30101701220ea5c0b6b611e643a1a3b17dee91c3c2d2840b7fb22a88c69bb0d756fab3d12ff"}{:hash "e30101701220da03e38c2311bf91a15f34287962f05d3fa887f0e0933560190fd6772df67cc3"}{:hash "e301017012204ead438798d856bbe63230a643f97590765ce2fe33f2490564dbaa3760d7a3ab"}{:hash "e3010170122047072b5a1440bc78d78acc5327532d009defdd2eacb4224998b28e3af1f672bf"}{:hash "e301017012200f87d517f8a061a1e276bbc26cd2dfc49ccf481b0f610cb774115ad4eaf4ce99"}{:hash "e3010170122086d32c547734610a3501fcfd574adfa6ec21e5ae25059d26d5431cfaa8b17127"}{:hash "e30101701220f26159a22a9d4cce610ee594d34f7a67c769aa18d2bfb600f94634dbe9fca917"}{:hash "e30101701220c9dad86937cd7d7bb7acc865d6daeb1d341547a761c504a9328104026d1b37e5"}]}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -0,0 +1,5 @@
{meta {:name "Birdies"
:author "olexx"
:thumbnail "e3010170122022ff7fbe598b50b9de52831f1f30a291e8a9a5b1ae549c21a47a0c5a4a76c8c1"
:preview "e301017012200df5d96c0792c162e9d9a368e315fb9a8718cf049defd93c5380e336fffd186d"
:stickers [{:hash "e3010170122096c5525b4251ae22abbc179282160e59d30b7843f29122a5b5f54c4edc0687f7"}{:hash "e301017012200fe2dbc8c2415aa8d13336987c580cb75bf173595136cda4b93c408a8bc698cb"}{:hash "e30101701220377eebcf8641b2bb715a27225fa70b75178f4a4db48b5bc160addd6cf2fd1c3c"}{:hash "e30101701220e3291484446842a665d1a3af4c50f66b0f434ea8d3d2690db41d6da2d9b3df57"}{:hash "e301017012201251f9963e14144183c81b05110f27a09492f08a52eb388e52dc815316982350"}{:hash "e30101701220f8435b33739dee56fec23ba5ec5b527d1d9a4e912742e00bb5773f090a3442e9"}{:hash "e30101701220a1b0e208e696afa0ccc3cfc66876e9c43706a0ae93daa12c4ab02c15c0a903c9"}{:hash "e3010170122059224ad38c272ae798b6c014da52a7d07e500ab980c1357591ff9c983f6a174e"}{:hash "e30101701220db692aadaa897f4027dae1914d6803b2a56d059d706efd967babd1bd5111804e"}{:hash "e30101701220205bc114b2358c5ca01f9e2e6122ab3eb92b1476b3f282ee2a274845a1ab4f43"}{:hash "e3010170122002260af10b74f767bbc5adfdbd5f83c8b0d7782aacef7321cd56fb8c4eb272ea"}{:hash "e30101701220fa37a013fc87760f23c8af91cc3150d4a1f8b57e7efb728149777a1a9237c2db"}{:hash "e30101701220ce694e2a6231127fa2442254a2d94f74d67b7fb594597707dd8861220d2a1313"}{:hash "e301017012203d22f03460e36419f23d8cd74ee09c7d0f5953ba66815605478030ba3ea0c629"}{:hash "e3010170122082a30dbc646dcfb5e447975e50d8c6f396420013e2df9ffe7d89c365cff1beac"}{:hash "e30101701220eea1c3d948182890e3a7528799199389dc2e49d944bd210f0c8a16848f4ceb39"}{:hash "e30101701220b398e1031b8f16ef7aa354b6775611c574b9ede053f98fbcac19f9bfebf0b7c9"}{:hash "e301017012203ea52166c85d25460a1d45b7ba2fcbddad7087af28137278caf1d0c4864c6b27"}{:hash "e301017012204e57cb55a9d6ce467fcbcc88d3ff156ea87f8213cbf8fb120347cf62eb563e2a"}{:hash "e30101701220e1cd4b47bfcca6032e4ca8a2aad030619e2328b8c56bc5d95017953947180c9c"}{:hash "e30101701220ef29467e5890172c26516299c62aaa767b70b2b1b17c9e7c53672942553367cc"}]}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -0,0 +1,5 @@
{meta {:name "Ghostatus"
:author "Brooklyn Design Factory"
:thumbnail "e30101701220a7beb4be086ad31ae19c64e5a832853571e239d9799a923a03779c4435c6fdad"
:preview "e3010170122027c67c9acbe98786f6db4aabca3fd3ec04993eaa3e08811aefe27d9786c3bf00"
:stickers [{:hash "e30101701220fff8527a1b37070d46c9077877b7f7cc74da5c31adafe77ba65e5efefebf5d91"}{:hash "e301017012208023d8c6bd327b0ac2be66423d59776a753d5f5492975fe0bd5b5601d7c1d9d3"}{:hash "e3010170122064f4e8fa00a5b8164689e038a4d74e0b12f4490dcd4112e80057c254f6fbc135"}{:hash "e301017012200d50bd618b0aed0562ed153de0bf77da766646e81a848982a2f8aaf7d7e94dcc"}{:hash "e3010170122055f08854a40acaac60355d9bb3eaa730b994e2e13484e67d2675103e0cda0c88"}{:hash "e301017012203fc2acfed328918bf000ee637ab4c25fa38f2c69b378b69b9212d61747d30c02"}{:hash "e3010170122096930b99e08c6c28c88c0b74bae7a0159f5c6438ab7d50294987533dabfee863"}{:hash "e3010170122051ddbe29bee4bbc5fcf50d81faad0872f32b88cea4e4e4fcdbf2daf5d09eda76"}{:hash "e301017012200647e07651c163515ce34d18b3c8636eeb4798dbaa1766b2a60facc59999b261"}{:hash "e30101701220c539bfa744e39cf2ece1ab379a15c95338d513a9ce5178d4ad28be486b801bc2"}{:hash "e301017012205ea333b9eb89918ed592f43372bd58dc3a91a7a71aa68b37369c2f66f931fd87"}{:hash "e3010170122007f05ba31bd77003bff562ed932a8b440de1ad05481dc622b1c0c571d6b39ffc"}{:hash "e30101701220906b7a664a87707db72921cf5c7416c61a717dfcb5fcff9bc04b28c612ae554d"}]}}

Some files were not shown because too many files have changed in this diff Show More