subtree updates

meta-raspberrypi: fde68b24f0..4c033eb074:
  Harunobu Kurokawa (1):
        rpi-cmdline, rpi-u-boot-src: Support USB boot

meta-arm: 0b61cc659a..4d22f982bc:
  Debbie Martin (2):
        arm-systemready: Add parted dependency and inherit testimage
        ci: Add Arm SystemReady firmware and IR ACS builds

  Harsimran Singh Tungal (3):
        arm-bsp/documentation: corstone1000: fix the steps in the user guide and instructions
        corstone1000:arm-bsp/optee: Update optee to v4.0
        corstone1000:arm-bsp/tftf: Fix tftf tests on mps3

  Jon Mason (5):
        arm/trusted-firmware-a: move patch file to bbappend
        arm/trusted-firmware-a: update to 2.10
        arm/hafnium: update to v2.10
        CI: rename meta-secure-core directory
        arm/edk2: update to 202311

  Ross Burton (1):
        CI: switch back to master

poky: 028b6f6226..4675bbb757:
  Adrian Freihofer (4):
        cmake-qemu.bbclass: make it more usable
        oe-selftest: add a cpp-example recipe
        oeqa/core/decorator: add skip if not qemu-usermode
        oe-selftest: add tests for C and C++ build tools

  Alassane Yattara (22):
        bitbake: toaster/test: bug-fix on tests/browser/test_all_builds_page
        bitbake: toaster/test: from test_no_builds_message.py wait for the empty state div to appear
        bitbake: toaster/test: delay driver action until elements to appear
        bitbake: toaster/tests: Ensure to kill toaster process create for tests functional
        bitbake: toaster/tests: Added functional/utils, contains useful methods using by functional tests
        bitbake: toaster/tests: Refactorize tests/functional
        bitbake: toaster/tests: Bug fixes, functional tests dependent on each other
        bitbake: toaster/tests: Fixes warnings in autobuilder
        bitbake: toaster/tests: bug-fix tests writing files into /tmp on the autobuilders
        bitbake: toaster/test: fix Copyright
        bitbake: toaster/tests: logging warning in console, trying to kill unavailable Runbuilds process
        bitbake: toaster/tests: Removed all time.sleep occurrence
        bitbake: toaster/tests: Bug-Fix testcase functional/test_project_page_tab_config.py
        bitbake: toaster/tests: bug-fix element click intercepted in browser/test_layerdetails_page.py
        bitbake: toaster/tests: Update tests/functional/functional_helpers test_functional_basic
        bitbake: toaster/tests: Fixes functional tests warning on autobuilder
        bitbake: toaster/tests: Bug-fix test_functional_basic, delay driver actions
        bitbake: toaster/tests: bug-fix An element matching "#projectstable" should be visible
        bitbake: toaster/tests: bug-fix An element matching "#lastest_builds" should be on the page
        bitbake: toaster/tests: Skip to show more then 100 item in ToasterTable
        bitbake: toaster/tests: Bug-fix "#project-created-notification" should be visible
        bitbake: toaster/toastergui: Bug-fix verify given layer path only if import/add local layer

  Alex Bennée (1):
        qemurunner: more cleanups for output blocking

  Alex Kiernan (17):
        cargo: Rename MANIFEST_PATH -> CARGO_MANIFEST_PATH
        cargo: Move CARGO_MANIFEST_PATH/CARGO_SRC_DIR to cargo_common
        rust: cargo: Convert single-valued variables to weak defaults
        cargo: Add CARGO_LOCK_PATH for path to Cargo.lock
        rust: Upgrade 1.70.0 -> 1.71.0
        rust: Upgrade 1.71.0 -> 1.71.1
        sstate-cache-management: Rewrite in python
        devtool: selftest: Fix test_devtool_modify_git_crates_subpath inequality
        devtool: selftest: Fix test_devtool_modify_git_crates_subpath bbappend check
        meta-selftest: hello-rs: Simple rust test recipe
        devtool: selftest: Swap to hello-rs for crates testing
        zvariant: Drop recipe
        rust: Upgrade 1.71.1 -> 1.72.0
        rust: Upgrade 1.72.0 -> 1.72.1
        rust: Upgrade 1.72.1 -> 1.73.0
        rust: Upgrade 1.73.0 -> 1.74.0
        rust: Upgrade 1.74.0 -> 1.74.1

  Alexander Kanavin (21):
        selftest/sstatetest: print output from bitbake with actual newlines, not \n
        selftest/sstatetests: do not delete custom $TMPDIRs under build-st when testing printdiff
        sstatesig/find_siginfo: special-case gcc-source when looking in sstate caches
        oeqa/selftest/sstatetests: re-work CDN tests, add local cache tests
        gobject-introspection: depend on setuptools to obtain distutils module
        libcap-ng-python: depend on setuptools to obtain distutils copy
        dnf: remove obsolete python3-gpg dependency (provided by gpgme)
        gpgme: disable python support (until upstream fixes 3.12 compatibility)
        python3-setuptools-rust: remove distutils dependency
        python3-babel: replace distutils with setuptools, as supported by upstream
        python3-pip: remove distutils depedency
        glib-2.0: replace distutils dependency with setuptools
        python3-pytest-runner: remove distutils dependency
        python3-numpy: distutils is no longer required
        bitbake: bitbake/codeparser.py: address ast module deprecations in py 3.12
        glibc-y2038-tests: do not run tests using 32 bit time APIs
        bitbake: bitbake/runqueue: add debugging for find_siginfo() calls
        bitbake: bitbake-diffsigs/runqueue: adapt to reworked find_siginfo()
        bitbake: bitbake/runqueue: prioritize local stamps over sstate signatures in printdiff
        sstatesig/find_siginfo: unify a disjointed API
        lib/sstatesig/find_siginfo: raise an error instead of returning None when obtaining mtime

  Alexander Lussier-Cullen (6):
        bitbake: toaster: fix pytest build test execution and test discovery
        bitbake: toaster: Add verbose printout for missing chrome(driver) dependencies
        bitbake: bitbake: toaster: add functional testing toaster error details
        bitbake: toaster/tests: Exit tests on chromedriver creation failure
        bitbake: toaster/tests: fix functional tests setup and teardown
        bitbake: toaster/tests: fix chrome argument syntax and wait for driver exit

  Alexandre Belloni (1):
        oeqa/selftest/recipetool: stop looking for md5sum

  Anuj Mittal (9):
        sqlite3: upgrade 3.44.0 -> 3.44.2
        base-passwd: upgrade 3.6.2 -> 3.6.3
        bluez5: upgrade 5.70 -> 5.71
        glib-2.0: upgrade 2.78.1 -> 2.78.3
        glib-networking: upgrade 2.76.1 -> 2.78.0
        puzzles: upgrade to latest revision
        stress-ng: upgrade 0.17.01 -> 0.17.03
        libusb1: fix upstream version check
        enchant2: upgrade 2.6.2 -> 2.6.4

  Archana Polampalli (1):
        bluez5: fix CVE-2023-45866

  Bruce Ashfield (31):
        linux-yocto/6.5: cfg: split runtime and symbol debug
        linux-yocto/6.5: update to v6.5.11
        linux-yocto/6.1: update to v6.1.62
        linux-yocto-dev: bump to v6.7
        linux-yocto/6.5: update to v6.5.12
        linux-yocto/6.5: update to v6.5.13
        linux-yocto/6.1: update to v6.1.65
        linux-yocto/6.1: drop removed IMA option
        linux-yocto/6.5: drop removed IMA option
        linux-yocto-rt/6.1: update to -rt18
        linux-yocto/6.1: update to v6.1.66
        linux-yocto/6.1: update to v6.1.67
        linux-yocto/6.5: fix AB-INT: QEMU kernel panic: No irq handler for vector
        linux-yocto/6.1: update to v6.1.68
        oeqa/runtime/parselogs: add qemux86 ACPI ignore for kernel v6.6+
        linux-libc-headers: update to v6.6-lts
        linux-yocto: introduce 6.6 reference kernel
        linux-yocto/6.6: fix AB-INT: QEMU kernel panic: No irq handler for vector
        linux-yocto-rt/6.6: fix CVE exclusion include
        linux-yocto/6.6: update CVE exclusions
        linux-yocto/6.6: update to v6.6.8
        linux-yocto/6.1: update to v6.1.69
        linux-yocto/6.5: drop 6.5 recipes
        linux-yocto-rt/6.6: correct meta data branch
        linux-yocto/6.6: update to v6.6.9
        linux-yocto/6.6: update CVE exclusions
        linux-yocto/6.1: update to v6.1.70
        linux-yocto/6.1: update CVE exclusions
        linux-yocto/6.6: ARM fix configuration audit warning
        linux-yocto/6.6: arm: jitter entropy backport
        poky/poky-tiny: make 6.6 the default kernel

  Changqing Li (1):
        man-pages: remove conflict pages

  Chen Qi (1):
        devtool: use straight print in check-upgrade-status output

  Clay Chang (1):
        devtool: deploy: provide max_process to strip_execs

  Daniel Ammann (1):
        base: Unpack .7z files with p7zip

  Deepthi Hemraj (1):
        autoconf: Add missing perl modules to RDEPENDS

  Dhairya Nagodra (2):
        cve-update-nvd2-native: faster requests with API keys
        cve-update-nvd2-native: increase the delay between subsequent request failures

  Eilís 'pidge' Ní Fhlannagáin (3):
        useradd: Fix issues with useradd dependencies
        useradd: Add testcase for bugzilla issue (currently disabled)
        usergrouptests.py: Add test for switching between static-ids

  Enrico Scholz (1):
        tcp-wrappers: drop libnsl2 build dependency

  Etienne Cordonnier (2):
        gdb/systemd: enable minidebuginfo support conditionally
        manuals: document minidebuginfo

  Fabio Estevam (3):
        libdrm: Upgrade to 2.4.119
        kmscube: Upgrade to latest revision
        bmap-tools: Upgrade to 3.7

  Hongxu Jia (2):
        socat: 1.7.4.4 -> 1.8.0.0
        man-db: 2.11.2 -> 2.12.0

  Jason Andryuk (3):
        linux-firmware: Package iwlwifi .pnvm files
        linux-firmware: Change bnx2 packaging
        linux-firmware: Create bnx2x subpackage

  Jeremy A. Puhlman (1):
        create-spdx-2.2: combine spdx can try to write before dir creation

  Jermain Horsman (2):
        lib/bblayers/makesetup.py: Remove unused imports
        lib/bblayers/buildconf.py: Remove unused imports/variables

  Jose Quaresma (2):
        go: update 1.20.10 -> 1.20.11
        go: update 1.20.11 -> 1.20.12

  Joshua Watt (11):
        bitbake: bitbake-hashserv: Add description of permissions
        bitbake.conf: Add runtimedir
        rpcbind: Specify state directory under /run
        libinput: Add packageconfig for tests
        ipk: Switch to using zstd compression
        lib/oe/path.py: Add relsymlink()
        lib/packagedata.py: Fix broken symlinks for providers with a '/'
        bitbake: contrib/vim: Syntax improvements
        classes-global/sstate: Fix variable typo
        lib/packagedata.py: Add API to iterate over rprovides
        classes-global/insane: Look up all runtime providers for file-rdeps

  Julien Stephan (19):
        recipetool: create_buildsys_python.py: initialize metadata
        recipetool: create: add trailing newlines
        recipetool: create: add new optional process_url callback for plugins
        recipetool: create_buildsys_python: add pypi support
        oeqa/selftest/recipetool: remove spaces on empty lines
        oeqa/selftest/recipetool/devtool: add test for pypi class
        recipetool: appendsrcfile(s): add dry-run mode
        recipeutils: bbappend_recipe: fix undefined variable
        recipeutils: bbappend_recipe: fix docstring
        recipeutils: bbappend_recipe: add a way to specify the name of the file to add
        recipeutils: bbappend_recipe: remove old srcuri entry if parameters are different
        recipetool: appendsrcfile(s): use params instead of extraline
        recipeutils: bbappend_recipe: allow to patch the recipe itself
        recipetool: appendsrcfile(s): add a mode to update the recipe itself
        oeqa/selftest/recipetool: appendsrfile: add test for machine
        oeqa/selftest/recipetool: appendsrc: add test for update mode
        oeqa/selftest/recipetool: add back checksum checks on pypi tests
        oeqa/selftest/recipetool: remove left over from development
        oeqa/selftest/recipetool: fix metadata corruption on meta layer

  Kevin Hao (2):
        beaglebone-yocto: Remove the redundant kernel-devicetree
        beaglebone-yocto: Remove the obsolete variables for uImage

  Khem Raj (13):
        tiff: Backport fixes for CVE-2023-6277
        kmod: Fix build with latest musl
        elfutils: Use own basename API implementation
        util-linux: Fix build with latest musl
        sysvinit: Include libgen.h for basename API
        attr: Fix build with latest musl
        opkg: Use own version of portable basename function
        util-linux: Delete md-raid tests
        gdb: Update to gdb 14.1 release
        systemd: Fix build with latest musl
        qemu: Fix build with latest musl
        qemu: Add packageconfig knob to enable pipewire support
        weston: Include libgen.h for basename

  Lee Chee Yang (5):
        migration-guides: reword fix in release-notes-4.3.1
        migration-guides: add release notes for 4.0.15
        perlcross: update to 1.5.2
        perl: 5.38.0 -> 5.38.2
        curl: update to 8.5.0

  Lucas Stach (1):
        mesa: upgrade 23.2.1 -> 23.3.1

  Ludovic Jozeau (1):
        image-live.bbclass: LIVE_ROOTFS_TYPE support compression

  Lukas Funke (1):
        selftest: wic: add test for zerorize option of empty plugin

  Malte Schmidt (1):
        wic: extend empty plugin with options to write zeros to partiton

  Markus Volk (3):
        gtk4: upgrade 4.12.3 -> 4.12.4
        libadwaita: update 1.4.0 -> 1.4.2
        appstream: Upgrade 0.16.3 -> 1.0.0

  Marlon Rodriguez Garcia (5):
        bitbake: toaster/tests: Update build test
        bitbake: toaster: Added new feature to import eventlogs from command line into toaster using replay functionality
        bitbake: toaster: remove test and update setup to avoid rebuilding image
        bitbake: toaster: Commandline build import table improvements
        bitbake: toaster: Added validation to stop import if there is a build in progress

  Marta Rybczynska (1):
        bitbake: toastergui: verify that an existing layer path is given

  Massimiliano Minella (1):
        zstd: fix LICENSE statement

  Michael Opdenacker (8):
        test-manual: text and formatting fixes
        test-manual: resource updates
        test-manual: use working example
        test-manual: add links to python unittest
        test-manual: explicit or fix file paths
        test-manual: add or improve hyperlinks
        dev-manual: runtime-testing: fix test module name
        poky.conf: update SANITY_TESTED_DISTROS to match autobuilder

  Mikko Rapeli (1):
        runqemu: match .rootfs. in addition to -image- for rootfs

  Ming Liu (1):
        grub: fs/fat: Don't error when mtime is 0

  Mingli Yu (2):
        python3-license-expression: Fix the ptest failure
        ptest-packagelists.inc: Add python3-license-expression

  Pavel Zhukov (2):
        bitbake: utils: Do not create directories with ${ in the name
        oeqa/selftest/bbtests: Add test for unexpanded variables in the dirname

  Peter Kjellerstedt (11):
        oeqa/selftest/devtool: Correct git clone of local repository
        oeqa/selftest/devtool: Avoid global Git hooks when amending a patch
        oeqa/selftest/devtool: Make test_devtool_load_plugin more resilient
        oeqa/selftest/recipetool: Make test_recipetool_load_plugin more resilient
        lib/oe/recipeutils: Avoid wrapping any SRC_URI[sha*sum] variables
        recipetool: create: Improve identification of licenses
        recipetool: create: Only include the expected SRC_URI checksums
        devtool: upgrade: Update all existing checksums for the SRC_URI
        devtool: modify: Make --no-extract work again
        devtool: modify: Handle recipes with a menuconfig task correctly
        dev-manual: Discourage the use of SRC_URI[md5sum]

  Peter Marko (1):
        dtc: preserve version also from shallow git clones

  Philip Balister (1):
        sanity.bbclass: Check for additional native perl modules.

  Renat Khalikov (1):
        python3-maturin: Add missing space appending to CFLAGS

  Richard Purdie (41):
        bitbake: runqueue: Improve inter setscene task dependency handling
        bitbake: bb/toaster: Fix assertEquals deprecation warnings
        bitbake: toaster: Fix assertRegexpMatches deprecation warnings
        bitbake: toastermain/settings: Avoid python filehandle closure warnings
        bitbake: toastergui: Fix regex markup issues
        bitbake: bitbake: Move to version 2.6.1 to mark runqueue changes
        bitbake: toaster-eventreplay: Remove ordering assumptions
        sanity.conf: Require bitbake 2.6.1 for recent runqueue change
        sstate: Remove unneeded code from setscene_depvalid() related to useradd
        oeqa/runtime/systemd: Ensure test runs only on systemd images
        bitbake: toaster: Update to use qemux86-64 machine by default
        bitbake: toaster/tests/builds: Add BB_HASHSERVE passthrough
        pseudo: Update to pull in syncfs probe fix
        useradd: Fix useradd do_populate_sysroot dependency bug
        sstate: Fix dir ownership issues in SSTATE_DIR
        oeqa/sstatetests: Disable gcc source printdiff test for now
        build-appliance-image: Update to master head revision
        bitbake: utils: Fix mkdir with PosixPath
        bitbake: runqueue: Remove tie between rqexe and starts_worker
        build-appliance-image: Update to master head revision
        testimage: Exclude wtmp from target-dumper commands
        qemurunner: Improve stdout logging handling
        qemurunner: Improve handling of serial port output blocking
        oeqa/selftest/overlayfs: Don't overwrite DISTRO_FEATURES
        testimage: Drop target_dumper and most of monitor_dumper
        oeqa/selftest/overlayfs: Fix whitespace
        qemu: Clean up DEPENDS
        qemu: Ensure pip and the python venv aren't used for meson
        curl: Disable two intermittently failing tests
        linux/cve-exclusion6.1: Update to latest kernel point release
        lib/prservice: Improve lock handling robustness
        oeqa/selftest/prservice: Improve test robustness
        scripts: Drop shell sstate-cache-management
        oeqa/selftest/sstatetests: Update sstate management script tests to python script
        curl: Disable test 1091 due to intermittent failures
        bitbake: lib/bb: Add workaround for libgcc issues with python 3.8 and 3.9
        bitbake: bitbake: Post release version bump to 2.7.0
        bitbake: siggen: Ensure version of siggen is verified
        bitbake: bitbake: Version bump for find_siginfo chanages
        sstatesig: Add version information for find_sigingfo
        sanity: Require bitbake 2.7.1

  Robert Berger (1):
        uninative-tarball.xz - reproducibility fix

  Robert Yang (5):
        gettext: Upgrade 0.22.3 -> 0.22.4
        nfs-utils: Upgrade 2.6.3 -> 2.6.4
        archiver.bbclass: Improve work-shared checking
        nfs-utils: Update Upstream-Status
        archiver.bbclass: Drop tarfile module to improve performance

  Ross Burton (23):
        avahi: update URL for new project location
        oeqa/runtime/parselogs: load ignores from disk
        oeqa/runtime/parselogs: migrate ignores
        meta-yocto-bsp/oeqa/parselogs: add BSP-specific ignores
        linux-yocto: update CVE exclusions
        genericx86: remove redundant assignments
        images: remove redundant IMAGE_BASENAME assignments
        insane: ensure more paths have the workdir removed
        tcl: skip timing-dependent tests in run-ptest
        qemurunner: remove unused import
        go: set vendor in CVE_PRODUCT
        runqemu: add qmp socket support
        linux-yocto: update CVE exclusions
        tcl: skip async and event tests in run-ptest
        images: add core-image-initramfs-boot
        machine/arch-armv9: remove crc and sve tunes, they are mandatory
        python3: re-enable profile guided optimisation
        openssl: mark assembler sections as call targets for PAC/BTI support on aarch64
        nativesdk: ensure features don't get backfilled
        nativesdk: don't unset MACHINE_FEATURES, let machine-sdk/ set it
        conf/machine-sdk: declare qemu-usermode SDK_MACHINE_FEATURE
        libseccomp: remove redundant PV assignment
        oeqa/parselogs-ignores-qemuarmv5: add comments and organise

  Saul Wold (1):
        package.py: OEHasPackage: Add MLPREFIX to packagename

  Shubham Kulkarni (1):
        tzdata: Upgrade to 2023d

  Simone Weiß (2):
        manuals: brief-yoctoprojectqs: align variable order with default local.conf
        patchtest: Add test for deprecated CVE_CHECK_IGNORE

  Soumya Sambu (1):
        ncurses: Fix - tty is hung after reset

  Sundeep KOKKONDA (1):
        rust: rustdoc reproducibility issue fix - disable PGO

  Tim Orling (12):
        python3-bcrypt: upgrade 4.0.1 -> 4.1.1
        python3-pygments: upgrade 2.16.1 -> 2.17.2
        recipetool: pypi: do not clobber SRC_URI checksums
        python3-setuptools-rust: BBCLASSEXTEND + nativesdk
        python3-maturin: add v1.4.0
        python3-maturin: bzip2-sys reproduciblility
        classes-recipe: add python_maturin.bbclass
        recipetool: add python_maturin support
        oe-selfest: add maturn runtime (testimage) test
        oeqa: add simple 'maturin' SDK (testsdk) test case
        oeqa: add "maturin develop" SDK test case
        oeqa: add runtime 'maturin develop' test case

  Tom Rini (1):
        inetutils: Update to the 2.5 release

  Trevor Gamblin (1):
        scripts/runqemu: fix regex escape sequences

  Victor Kamensky (5):
        systemtap: upgrade 4.9 -> 5.0
        systemtap: do not install uprobes and uprobes sources
        systemtap-uprobes: removed as obsolete
        systemtap: explicit handling debuginfod library dependency
        systemtap: fix libdebuginfod auto detection logic

  Vijay Anusuri (1):
        avahi: backport CVE-2023-1981 & CVE's follow-up patches

  Viswanath Kraleti (2):
        image-uefi.conf: Add EFI_UKI_PATH variable
        systemd-boot: Add recipe to compile native

  Wang Mingyu (38):
        kbd: upgrade 2.6.3 -> 2.6.4
        libatomic-ops: upgrade 7.8.0 -> 7.8.2
        libnl: upgrade 3.8.0 -> 3.9.0
        libseccomp: upgrade 2.5.4 -> 2.5.5
        libva-utils: upgrade 2.20.0 -> 2.20.1
        dnf: upgrade 4.18.1 -> 4.18.2
        gpgme: upgrade 1.23.1 -> 1.23.2
        kea: upgrade 2.4.0 -> 2.4.1
        opkg-utils: upgrade 0.6.2 -> 0.6.3
        repo: upgrade 2.39 -> 2.40
        sysstat: upgrade 12.7.4 -> 12.7.5
        p11-kit: upgrade 0.25.2 -> 0.25.3
        python3-babel: upgrade 2.13.1 -> 2.14.0
        python3-dbusmock: upgrade 0.29.1 -> 0.30.0
        python3-hatchling: upgrade 1.18.0 -> 1.20.0
        python3-hypothesis: upgrade 6.90.0 -> 6.92.1
        python3-importlib-metadata: upgrade 6.8.0 -> 7.0.0
        python3-license-expression: upgrade 30.1.1 -> 30.2.0
        python3-pathspec: upgrade 0.11.2 -> 0.12.1
        python3-pip: upgrade 23.3.1 -> 23.3.2
        python3-psutil: upgrade 5.9.6 -> 5.9.7
        python3-pytest-runner: upgrade 6.0.0 -> 6.0.1
        python3-trove-classifiers: upgrade 2023.11.22 -> 2023.11.29
        python3-typing-extensions: upgrade 4.8.0 -> 4.9.0
        python3-wcwidth: upgrade 0.2.11 -> 0.2.12
        ttyrun: upgrade 2.29.0 -> 2.30.0
        xwayland: upgrade 23.2.2 -> 23.2.3
        diffoscope: upgrade 252 -> 253
        iputils: upgrade 20221126 -> 20231222
        gstreamer1.0: upgrade 1.22.7 -> 1.22.8
        dhcpcd: upgrade 10.0.5 -> 10.0.6
        fontconfig: upgrade 2.14.2 -> 2.15.0
        python3-setuptools: upgrade 69.0.2 -> 69.0.3
        python3-dbusmock: upgrade 0.30.0 -> 0.30.1
        python3-hatchling: upgrade 1.20.0 -> 1.21.0
        python3-importlib-metadata: upgrade 7.0.0 -> 7.0.1
        python3-lxml: upgrade 4.9.3 -> 4.9.4
        aspell: upgrade 0.60.8 -> 0.60.8.1

  Yash Shinde (1):
        rust: Disable rust oe-selftest

  Yi Zhao (3):
        json-glib: upgrade 1.6.6 -> 1.8.0
        psplash: upgrade to latest revision
        debianutils: upgrade 5.14 -> 5.15

  Yoann Congal (2):
        lib/oe/patch: handle creating patches for CRLF sources
        strace: Disable bluetooth support by default

  Zang Ruochen (2):
        ell: upgrade 0.60 -> 0.61
        musl: add typedefs for Elf64_Relr and Elf32_Relr

  Zoltan Boszormenyi (1):
        update_gtk_icon_cache: Fix for GTK4-only builds

  venkata pyla (1):
        wic: use E2FSPROGS_FAKE_TIME and hash_seed to generate reproducible ext4 images

meta-openembedded: 5ad7203f68..7d8115d550:
  Alex Kiernan (7):
        mdns: Fix HOMEPAGE URL
        mbedtls: Upgrade 3.5.0 -> 3.5.1
        c-ares: Upgrade 1.22.1 -> 1.24.0
        mdns: Upgrade 2200.40.37.0.1 -> 2200.60.25.0.4
        c-ares: Move to tarballs, add ptest and static support
        thin-provisioning-tools: Upgrade 1.0.4 -> 1.0.9
        bearssl: Upgrade to latest

  Alexander Kanavin (29):
        python3-pyinotify: remove as unmaintained
        python3-supervisor: do not rely on smtpd module
        python3-meld3: do not rely on smtpd module
        python3-m2crypto: do not rely on smtpd module
        python3-uinput: remove as unmaintained
        python3-mcrypto: rely on setuptools for distutils copy
        python3-joblib: do not rely in distutils
        python3-web3: remove distutils dependency
        python3-cppy: remove unused distutils dependency
        python3-pyroute2: remove unused distutils dependency
        python3-eventlet: backport a patch to remove distutils dependency
        python3-unoconv: rely on setuptools to obtain distutils copy
        python3-astroid: remove unneeded distutils dependency
        python3-django: remove unneeded distutils dependency
        python3-pillow: remove unneeded distutils dependency
        python3-grpcio: update 1.56.2 -> 1.59.3
        gstd: correctly delete files in do_install
        libplist: fix python 3.12 compatibility
        libcamera: skip until upstream resolves python 3.12 compatibility
        nodejs: backport (partially) python 3.12 support
        nodejs: backport (partially) python 3.12 support
        polkit: remove long obsolete 0.119 version
        mozjs-115: split the way-too-long PYTHONPATH line
        polkit: update mozjs dependency 102 -> 115
        mozjs-115: backport py 3.12 compatibility
        mozjs-102: remove the recipe
        gthumb: update 3.12.2 -> 3.12.4
        flatpak: do not rely on executables from the host
        bolt: package systemd units

  Archana Polampalli (1):
        cjson: upgrade 1.7.16 -> 1.7.17

  Bruce Ashfield (1):
        zfs: update to 2.2.2

  Changqing Li (2):
        postgresql: upgrade 15.4 -> 15.5
        redis: upgrade 6.2.13 -> 6.2.14

  Derek Straka (70):
        python3-greenlet: update to version 3.0.2
        python3-ujson: update to version 5.9.0
        python3-termcolor: update to version 2.4.0
        python3-cmake: update to version 3.28.0
        python3-pint: upgrade to 0.23
        python3-gnupg: update to 0.5.2
        python3-pyzmq: update to 25.1.2
        python3-tox: update to version 4.11.4
        python3-olefile: update to version 0.47
        python3-distlib: update to version 0.3.8
        python3-colorlog: update to version 6.8.0
        python3-pymongo: update version to 4.6.1
        python3-bandit: update to version 1.7.6
        python3-gmqtt: update to version 0.6.13
        python3-portion: update to version 2.4.2
        python3-prompt-toolkit: update to version 3.0.43
        python3-asyncinotify: update to version 4.0.4
        python3-bitstring: update to version 4.1.4
        python3-ipython: update to version 8.18.1
        nginx: update versions for both the stable branch and mainline
        python3-portalocker: update to version 2.8.2
        python3-astroid: update to version 3.0.2
        python3-alembic: update to version 1.13.1
        python3-pymisp: update to verion 2.4.182
        python3-ninja: update to version 1.11.1.1
        python3-coverage: update to version 7.3.4
        python3-pdm: update to version 2.11.1
        python3-paramiko: update to version 3.4.0
        python3-zeroconf: update to version 0.131.0
        python3-wtforms: update to version 3.1.1
        python3-isort: update to version 5.13.2
        python3-protobuf: update to version 4.25.1
        python3-lazy-object-proxy: update to version 1.10.0
        python3-cantools: update to version 39.4.0
        python3-sentry-sdk: update to version 1.39.1
        python3-xmlschema: update to version 2.5.1
        python3-apiflask: update to version 2.1.0
        python3-rapidjson: update to version 1.14
        python3-bitarray: update to version 2.9.0
        python3-pyfanotify: update to version 0.2.2
        python3-eventlet: update to version 0.34.1
        python3-flask-wtf: update to version 1.2.1
        python3-grpcio: update to version 1.60.0
        python3-grpcio-tools: update to version 1.60.0
        python3-cmake: update to version 3.28.1
        python3-flask-sqlalchemy: fix upstream uri check
        python3-wtforms: fix upstream uri and version check
        gyp: update to the latest commit
        python3-ipython-genutils: fix upstream uri and version check
        python3-flask: fix upstream uri and version check
        python3-wpa-supplicant: fix upstream uri and version check
        python3-uswid: update to version 0.4.7
        python3-flask-wtf: fix upstream uri and version check
        python3-gspread: update to version 5.12.3
        python3-pytest-html: update to version 4.1.1
        python3-setuptools-scm-git-archive: remove obsolete package
        python3-pyroute2: update to version 0.7.10
        python3-constantly: update to version 23.10.4
        python3-mypy: update to version 1.8.0
        python3-flask-jwt-extended: update to version 4.6.0
        python3-greenlet: update to version 3.0.3
        python3-web3: update to version 6.13.0
        python3-parse: update to version 1.20.0
        python3-kmod: add comment about update to version 0.9.2
        python3-engineio: update to version 4.8.1
        python3-sqlalchemy: update to version 2.0.24
        python3-pdm-backend: update to version 2.1.8
        python3-cantools: update to version 39.4.1
        python3-argh: update to version 0.30.5
        python3-dominate: update to version 2.9.1

  Dmitry Baryshkov (2):
        android-tools: remove two Debianisms
        networkmanager: drop libnewt dependency

  Frederic Martinsons (3):
        crash: factorize recipe with inc file to prepare cross-canadian version
        crash: add cross canadian version
        crash: update to 8.0.4

  Jan Vermaete (1):
        netdata: added Python as rdepends

  Jean-Marc BOUCHE (1):
        terminus-font: build compressed archives with -n

  Jose Quaresma (1):
        ostree: Upgrade 2023.7 -> 2023.8

  Joshua Watt (1):
        redis: Create state directory in systemd service

  Jörg Sommer (1):
        i2cdev: New recipe with i2c tools

  Kai Kang (1):
        lvm2: 2.03.16 -> 2.03.22

  Khem Raj (3):
        Revert "nodejs: backport (partially) python 3.12 support"
        Revert "libcamera: skip until upstream resolves python 3.12 compatibility"
        libcamera: Fix build with python 3.12

  Leon Anavi (11):
        sip: Upgrade 6.7.12 -> 6.8.0
        python3-expandvars: add recipe
        python3-frozenlist: upgrade 1.4.0 -> 1.4.1
        python3-yarl: upgrade 1.9.2 -> 1.9.4
        python3-coverage: upgrade 7.3.2 -> 7.3.3
        python3-cycler: upgrade 0.11.0 -> 0.12.1
        python3-aiohue: upgrade 4.6.2 -> 4.7.0
        python3-sdbus: upgrade 0.11.0 -> 0.11.1
        python3-zeroconf: upgrade 0.128.4 -> 0.130.0
        python3-dominate: upgrade 2.8.0 -> 2.9.0
        python3-rlp: upgrade 3.0.0 -> 4.0.0

  Marek Vasut (1):
        faad2: Upgrade 2.10.0 -> 2.11.1

  Markus Volk (3):
        wireplumber: update 0.4.15 -> 0.4.17
        tracker: dont inherit gsettings
        gnome-software: update 45.1 -> 45.2

  Martin Jansa (4):
        monocypher: pass LIBDIR to fix installed-vs-shipped QA issue with multilib
        rygel: fix build with gtk+3 PACKAGECONFIG disabled
        rygel: add x11 to DISTRO_FEATURES
        driverctl: fix installed-vs-shipped

  Meenali Gupta (1):
        nginx: upgrade 1.25.2 -> 1.25.3

  Mingli Yu (2):
        mariadb: Upgrade to 10.11.6
        tk: Remove buildpath issue

  Nathan BRIENT (1):
        cyaml: new recipe

  Niko Mauno (1):
        pkcs11-provider: Add recipe

  Ny Antra Ranaivoarison (1):
        python3-click-spinner: backport patch that fixes deprecated methods

  Patrick Wicki (1):
        poco: upgrade 1.12.4 -> 1.12.5p2

  Petr Chernikov (1):
        abseil-cpp: remove -Dcmake_cxx_standard=14 flag from extra_oecmake

  Robert Yang (1):
        minifi-cpp: Fix do_configure error builder aarch64

  Ross Burton (13):
        Remove unused SRC_DISTRIBUTE_LICENSES
        gspell: inherit gtk-doc
        gspell: update DEPENDS, switch iso-codes for icu
        librest: remove spurious build dependencies
        librest: inherit gtk-doc
        keybinder: use autotools-brokensep instead of setting B
        keybinder: disable gtk-doc documentation
        gtksourceview3: remove obsolete DEPENDS
        libgsf: remove obsolete DEPENDS
        evolution-data-server: remove obsolete intltool DEPENDS
        php: remove lemon-native build dependency
        lemon: upgrade to 3.44.2
        renderdoc: no need to depend on vim-native

  Samuli Piippo (1):
        jasper: enable opengl only wih x11

  Theodore A. Roth (1):
        python3-flask-sqlalchemy: upgrade 2.5.1 -> 3.1.1

  Thomas Perrot (2):
        networkmanager: add missing modemmanager rdepends
        networkmanager: fix some missing pkgconfig

  Tim Orling (8):
        python3-pydantic-core: add v2.14.5
        python3-annotated-types: add v0.6.0
        python3-pydantic: fix RDEPENDS
        python3-dirty-equals: add v0.7.1
        python3-pydantic-core: enable ptest
        python3-cloudpickle: add v3.0.0
        python3-pydantic: enable ptest
        python3-yappi: upgrade 1.4.0 -> 1.6.0; fix ptests

  Wang Mingyu (61):
        python3-alembic: upgrade 1.12.1 -> 1.13.0
        python3-ansi2html: upgrade 1.8.0 -> 1.9.1
        python3-argcomplete: upgrade 3.1.6 -> 3.2.1
        python3-dbus-fast: upgrade 2.15.0 -> 2.21.0
        python3-django: upgrade 4.2.7 -> 5.0
        python3-flask-restx: upgrade 1.2.0 -> 1.3.0
        python3-google-api-core: upgrade 2.14.0 -> 2.15.0
        python3-google-api-python-client: upgrade 2.108.0 -> 2.111.0
        python3-googleapis-common-protos: upgrade 1.61.0 -> 1.62.0
        python3-google-auth: upgrade 2.23.4 -> 2.25.2
        python3-imageio: upgrade 2.33.0 -> 2.33.1
        python3-isort: upgrade 5.12.0 -> 5.13.1
        python3-path: upgrade 16.7.1 -> 16.9.0
        python3-platformdirs: upgrade 4.0.0 -> 4.1.0
        python3-pytest-asyncio: upgrade 0.22.0 -> 0.23.2
        python3-sentry-sdk: upgrade 1.37.1 -> 1.39.0
        python3-bitarray: upgrade 2.8.3 -> 2.8.5
        python3-eth-keyfile: upgrade 0.6.1 -> 0.7.0
        python3-eth-rlp: upgrade 0.3.0 -> 1.0.0
        python3-fastnumbers: upgrade 5.0.1 -> 5.1.0
        python3-pylint: upgrade 3.0.2 -> 3.0.3
        python3-tornado: upgrade 6.3.3 -> 6.4
        python3-traitlets: upgrade 5.13.0 -> 5.14.0
        python3-types-setuptools: upgrade 68.2.0.2 -> 69.0.0.0
        python3-virtualenv: upgrade 20.24.7 -> 20.25.0
        python3-web3: upgrade 6.11.3 -> 6.12.0
        python3-websocket-client: upgrade 1.6.4 -> 1.7.0
        python3-zeroconf: upgrade 0.127.0 -> 0.128.4
        ctags: upgrade 6.0.20231126.0 -> 6.0.20231210.0
        gensio: upgrade 2.8.0 -> 2.8.2
        hwdata: upgrade 0.376 -> 0.377
        lvgl: upgrade 8.3.10 -> 8.3.11
        gjs: upgrade 1.78.0 -> 1.78.1
        ifenslave: upgrade 2.13 -> 2.14
        libei: upgrade 1.1.0 -> 1.2.0
        pkcs11-helper: upgrade 1.29.0 -> 1.30.0
        strongswan: upgrade 5.9.12 -> 5.9.13
        webkitgtk3: upgrade 2.42.2 -> 2.42.3
        sip: upgrade 6.8.0 -> 6.8.1
        paho-mqtt-cpp: upgrade 1.3.1 -> 1.3.2
        dbus-cxx: upgrade 2.4.0 -> 2.5.0
        exiftool: upgrade 12.70 -> 12.71
        uftp: upgrade 5.0.2 -> 5.0.3
        ctags: upgrade 6.0.20231210.0 -> 6.0.20231224.0
        jasper: Fix install conflict when enable multilib.
        jq: upgrade 1.7 -> 1.7.1
        libmbim: upgrade 1.31.1 -> 1.31.2
        libqmi: upgrade 1.34.0 -> 1.35.1
        opencl-headers: upgrade 2023.04.17 -> 2023.12.14
        valijson: upgrade 1.0.1 -> 1.0.2
        python3-apispec: upgrade 6.3.0 -> 6.3.1
        python3-asyncinotify: upgrade 4.0.4 -> 4.0.5
        python3-bitarray: upgrade 2.9.0 -> 2.9.1
        python3-cassandra-driver: upgrade 3.28.0 -> 3.29.0
        python3-ipython: upgrade 8.18.1 -> 8.19.0
        python3-pydantic: upgrade 2.5.2 -> 2.5.3
        python3-regex: upgrade 2023.10.3 -> 2023.12.25
        opencl-icd-loader: upgrade 2023.04.17 -> 2023.12.14
        python3-distro: upgrade 1.8.0 -> 1.9.0
        zchunk: upgrade 1.3.2 -> 1.4.0
        python3-eventlet: upgrade 0.34.1 -> 0.34.2

  William Lyu (1):
        networkmanager: Improved SUMMARY and added DESCRIPTION

  Xiangyu Chen (1):
        layer.conf: add libbpf to NON_MULTILIB_RECIPES

  Yi Zhao (2):
        open-vm-tools: upgrade 12.1.5 -> 12.3.5
        samba: upgrade 4.18.8 -> 4.18.9

  Zoltán Böszörményi (2):
        mutter: Make gnome-desktop and libcanberra dependencies optional
        zenity: Upgrade to 4.0.0

  alperak (29):
        jasper: upgrade 2.0.33 -> 4.1.1
        xcursorgen: upgrade 1.0.7 -> 1.0.8
        xstdcmap: upgrade 1.0.4 -> 1.0.5
        xlsclients: upgrade 1.1.4 -> 1.1.5
        xlsatoms: upgrade 1.1.3 -> 1.1.4
        xkbevd: upgrade 1.1.4 -> 1.1.5
        xgamma: upgrade 1.0.6 -> 1.0.7
        sessreg: upgrade 1.1.2 -> 1.1.3
        xbitmaps: upgrade 1.1.2 -> 1.1.3
        xcursor-themes: add recipe
        xorg-docs: add recipe
        xorg-sgml-doctools: update summary depends and inc file
        xf86-video-ati: upgrade 19.1.0 -> 22.0.0
        xf86-input-void: upgrade 1.4.1 -> 1.4.2
        libxaw: upgrade 1.0.14 -> 1.0.15
        xf86-video-mga: upgrade 2.0.0 -> 2.0.1
        snappy: upgrade 1.1.9 -> 1.1.10
        xsetroot: upgrade 1.1.2 -> 1.1.3
        libbytesize: Removed unnecessary setting of B
        libmxml: use autotools-brokensep instead of setting B
        libsombok3: use autotools-brokensep instead of setting B
        pgpool2: use autotools-brokensep instead of setting B
        qpdf: upgrade 11.6.3 -> 11.6.4
        cpprest: upgrade 2.10.18 -> 2.10.19
        avro-c: upgrade 1.11.2 -> 1.11.3
        dool: upgrade 1.1.0 -> 1.3.1
        driverctl: upgrade 0.111 -> 0.115
        hstr: upgrade 2.5.0 -> 3.1.0
        libharu: upgrade 2.3.0 -> 2.4.4

meta-security: 070a1e82cc..b2e1511338:
  Armin Kuster (6):
        libgssglue: update to 0.8
        python3-privacyidea: Update to 3.9.1
        lynis: Update SRC_URI to improve updater
        layers: Move READMEs to markdown format
        arpwatch: adjust CONFIGURE params to allow to build again.
        python3-pyinotify: fail2ban needs this module

  Dawid Dabrowski (1):
        libhoth recipe update

  Erik Schilling (2):
        dm-verity-img.bbclass: use bc-native
        dm-verity-img.bbclass: remove IMAGE_NAME_SUFFIX

  Mikko Rapeli (2):
        tpm2-tss: support native builds
        dm-verity-img.bbclass: add DM_VERITY_DEPLOY_DIR

Change-Id: I94d7f1ee5ff2da4555c05fbf63a1293ec8f249c2
Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
diff --git a/poky/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py b/poky/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py
index d9ea7fd..562fede 100644
--- a/poky/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py
+++ b/poky/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py
@@ -19,12 +19,15 @@
 import time
 import unittest
 
+import pytest
 from selenium import webdriver
+from selenium.webdriver.support import expected_conditions as EC
 from selenium.webdriver.support.ui import WebDriverWait
 from selenium.webdriver.common.by import By
 from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
 from selenium.common.exceptions import NoSuchElementException, \
-        StaleElementReferenceException, TimeoutException
+        StaleElementReferenceException, TimeoutException, \
+        SessionNotCreatedException
 
 def create_selenium_driver(cls,browser='chrome'):
     # set default browser string based on env (if available)
@@ -34,12 +37,31 @@
 
     if browser == 'chrome':
         options = webdriver.ChromeOptions()
-        options.add_argument('headless')
+        options.add_argument('--headless')
         options.add_argument('--disable-infobars')
         options.add_argument('--disable-dev-shm-usage')
         options.add_argument('--no-sandbox')
         options.add_argument('--remote-debugging-port=9222')
-        return webdriver.Chrome(options=options)
+        try:
+            return webdriver.Chrome(options=options)
+        except SessionNotCreatedException as e:
+            exit_message = "Halting tests prematurely to avoid cascading errors."
+            # check if chrome / chromedriver exists
+            chrome_path = os.popen("find ~/.cache/selenium/chrome/ -name 'chrome' -type f -print -quit").read().strip()
+            if not chrome_path:
+                pytest.exit(f"Failed to install/find chrome.\n{exit_message}")
+            chromedriver_path = os.popen("find ~/.cache/selenium/chromedriver/ -name 'chromedriver' -type f -print -quit").read().strip()
+            if not chromedriver_path:
+                pytest.exit(f"Failed to install/find chromedriver.\n{exit_message}")
+            # check if depends on each are fulfilled
+            depends_chrome = os.popen(f"ldd {chrome_path} | grep 'not found'").read().strip()
+            if depends_chrome:
+                pytest.exit(f"Missing chrome dependencies.\n{depends_chrome}\n{exit_message}")
+            depends_chromedriver = os.popen(f"ldd {chromedriver_path} | grep 'not found'").read().strip()
+            if depends_chromedriver:
+                pytest.exit(f"Missing chromedriver dependencies.\n{depends_chromedriver}\n{exit_message}")
+            # print original error otherwise
+            pytest.exit(f"Failed to start chromedriver.\n{e}\n{exit_message}")
     elif browser == 'firefox':
         return webdriver.Firefox()
     elif browser == 'marionette':
@@ -145,6 +167,8 @@
         """ Clean up webdriver driver """
 
         cls.driver.quit()
+        # Allow driver resources to be properly freed before proceeding with further tests
+        time.sleep(5)
         super(SeleniumTestCaseBase, cls).tearDownClass()
 
     def get(self, url):
@@ -182,6 +206,8 @@
         is_present = lambda driver: self.find(selector)
         msg = 'An element matching "%s" should be on the page' % selector
         element = Wait(self.driver, poll=poll).until(is_present, msg)
+        if poll > 2:
+            time.sleep(poll)  # element need more delay to be present
         return element
 
     def wait_until_visible(self, selector, poll=1):
@@ -192,6 +218,19 @@
         time.sleep(poll)  # wait for visibility to settle
         return self.find(selector)
 
+    def wait_until_clickable(self, selector, poll=1):
+        """ Wait until element matching CSS selector is visible on the page """
+        WebDriverWait(
+            self.driver,
+            Wait._TIMEOUT,
+            poll_frequency=poll
+        ).until(
+            EC.element_to_be_clickable((By.ID, selector.removeprefix('#')
+                                        )
+                                       )
+        )
+        return self.find(selector)
+
     def wait_until_focused(self, selector):
         """ Wait until element matching CSS selector has focus """
         is_focused = \
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_all_builds_page.py b/poky/bitbake/lib/toaster/tests/browser/test_all_builds_page.py
index 4e9b9fd..7019b3d 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_all_builds_page.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_all_builds_page.py
@@ -7,8 +7,8 @@
 # SPDX-License-Identifier: GPL-2.0-only
 #
 
+import os
 import re
-import time
 
 from django.urls import reverse
 from selenium.webdriver.support.select import Select
@@ -28,7 +28,8 @@
     CLI_BUILDS_PROJECT_NAME = 'command line builds'
 
     def setUp(self):
-        bbv = BitbakeVersion.objects.create(name='bbv1', giturl='/tmp/',
+        builldir = os.environ.get('BUILDDIR', './')
+        bbv = BitbakeVersion.objects.create(name='bbv1', giturl=f'{builldir}/',
                                             branch='master', dirpath='')
         release = Release.objects.create(name='release1',
                                          bitbake_version=bbv)
@@ -74,7 +75,7 @@
             '[data-role="data-recent-build-buildtime-field"]' % build.id
 
         # because this loads via Ajax, wait for it to be visible
-        self.wait_until_present(selector)
+        self.wait_until_visible(selector)
 
         build_time_spans = self.find_all(selector)
 
@@ -84,7 +85,7 @@
 
     def _get_row_for_build(self, build):
         """ Get the table row for the build from the all builds table """
-        self.wait_until_present('#allbuildstable')
+        self.wait_until_visible('#allbuildstable')
 
         rows = self.find_all('#allbuildstable tr')
 
@@ -174,7 +175,7 @@
 
         url = reverse('all-builds')
         self.get(url)
-        self.wait_until_present('td[class="target"]')
+        self.wait_until_visible('td[class="target"]')
 
         cell = self.find('td[class="target"]')
         content = cell.get_attribute('innerHTML')
@@ -198,8 +199,8 @@
         self.get(url)
 
         # should see a rebuild button for non-command-line builds
+        self.wait_until_visible('#allbuildstable tbody tr')
         selector = 'div[data-latest-build-result="%s"] .rebuild-btn' % build1.id
-        time.sleep(2)
         run_again_button = self.find_all(selector)
         self.assertEqual(len(run_again_button), 1,
                          'should see a rebuild button for non-cli builds')
@@ -260,25 +261,25 @@
         element = self._get_build_time_element(build1)
         links = element.find_elements(By.CSS_SELECTOR, 'a')
         msg = 'should be a link on the build time for a successful recent build'
-        self.assertEquals(len(links), 1, msg)
+        self.assertEqual(len(links), 1, msg)
 
         # test recent builds area for failed build
         element = self._get_build_time_element(build2)
         links = element.find_elements(By.CSS_SELECTOR, 'a')
         msg = 'should not be a link on the build time for a failed recent build'
-        self.assertEquals(len(links), 0, msg)
+        self.assertEqual(len(links), 0, msg)
 
         # test the time column for successful build
         build1_row = self._get_row_for_build(build1)
         links = build1_row.find_elements(By.CSS_SELECTOR, 'td.time a')
         msg = 'should be a link on the build time for a successful build'
-        self.assertEquals(len(links), 1, msg)
+        self.assertEqual(len(links), 1, msg)
 
         # test the time column for failed build
         build2_row = self._get_row_for_build(build2)
         links = build2_row.find_elements(By.CSS_SELECTOR, 'td.time a')
         msg = 'should not be a link on the build time for a failed build'
-        self.assertEquals(len(links), 0, msg)
+        self.assertEqual(len(links), 0, msg)
 
     def test_builds_table_search_box(self):
         """ Test the search box in the builds table on the all builds page """
@@ -288,7 +289,7 @@
         self.get(url)
 
         # Check search box is present and works
-        self.wait_until_present('#allbuildstable tbody tr')
+        self.wait_until_visible('#allbuildstable tbody tr')
         search_box = self.find('#search-input-allbuildstable')
         self.assertTrue(search_box.is_displayed())
 
@@ -296,24 +297,37 @@
         search_box.send_keys('foo')
         search_btn = self.find('#search-submit-allbuildstable')
         search_btn.click()
-        self.wait_until_present('#allbuildstable tbody tr')
+        self.wait_until_visible('#allbuildstable tbody tr')
         rows = self.find_all('#allbuildstable tbody tr')
         self.assertTrue(len(rows) >= 1)
 
     def test_filtering_on_failure_tasks_column(self):
         """ Test the filtering on failure tasks column in the builds table on the all builds page """
+        def _check_if_filter_failed_tasks_column_is_visible():
+            # check if failed tasks filter column is visible, if not click on it
+            # Check edit column
+            edit_column = self.find('#edit-columns-button')
+            self.assertTrue(edit_column.is_displayed())
+            edit_column.click()
+            # Check dropdown is visible
+            self.wait_until_visible('ul.dropdown-menu.editcol')
+            filter_fails_task_checkbox = self.find('#checkbox-failed_tasks')
+            if not filter_fails_task_checkbox.is_selected():
+                filter_fails_task_checkbox.click()
+            edit_column.click()
+
         self._get_create_builds(success=10, failure=10)
 
         url = reverse('all-builds')
         self.get(url)
 
         # Check filtering on failure tasks column
-        self.wait_until_present('#allbuildstable tbody tr')
+        self.wait_until_visible('#allbuildstable tbody tr')
+        _check_if_filter_failed_tasks_column_is_visible()
         failed_tasks_filter = self.find('#failed_tasks_filter')
         failed_tasks_filter.click()
         # Check popup is visible
-        time.sleep(1)
-        self.wait_until_present('#filter-modal-allbuildstable')
+        self.wait_until_visible('#filter-modal-allbuildstable')
         self.assertTrue(
             self.find('#filter-modal-allbuildstable').is_displayed())
         # Check that we can filter by failure tasks
@@ -322,7 +336,7 @@
         build_without_failure_tasks.click()
         # click on apply button
         self.find('#filter-modal-allbuildstable .btn-primary').click()
-        self.wait_until_present('#allbuildstable tbody tr')
+        self.wait_until_visible('#allbuildstable tbody tr')
         # Check if filter is applied, by checking if failed_tasks_filter has btn-primary class
         self.assertTrue(self.find('#failed_tasks_filter').get_attribute(
             'class').find('btn-primary') != -1)
@@ -335,12 +349,11 @@
         self.get(url)
 
         # Check filtering on failure tasks column
-        self.wait_until_present('#allbuildstable tbody tr')
+        self.wait_until_visible('#allbuildstable tbody tr')
         completed_on_filter = self.find('#completed_on_filter')
         completed_on_filter.click()
         # Check popup is visible
-        time.sleep(1)
-        self.wait_until_present('#filter-modal-allbuildstable')
+        self.wait_until_visible('#filter-modal-allbuildstable')
         self.assertTrue(
             self.find('#filter-modal-allbuildstable').is_displayed())
         # Check that we can filter by failure tasks
@@ -349,28 +362,26 @@
         build_without_failure_tasks.click()
         # click on apply button
         self.find('#filter-modal-allbuildstable .btn-primary').click()
-        self.wait_until_present('#allbuildstable tbody tr')
+        self.wait_until_visible('#allbuildstable tbody tr')
         # Check if filter is applied, by checking if completed_on_filter has btn-primary class
         self.assertTrue(self.find('#completed_on_filter').get_attribute(
             'class').find('btn-primary') != -1)
 
         # Filter by date range
         self.find('#completed_on_filter').click()
-        self.wait_until_present('#filter-modal-allbuildstable')
+        self.wait_until_visible('#filter-modal-allbuildstable')
         date_ranges = self.driver.find_elements(
             By.XPATH, '//input[@class="form-control hasDatepicker"]')
         today = timezone.now()
         yestersday = today - timezone.timedelta(days=1)
-        time.sleep(1)
         date_ranges[0].send_keys(yestersday.strftime('%Y-%m-%d'))
         date_ranges[1].send_keys(today.strftime('%Y-%m-%d'))
         self.find('#filter-modal-allbuildstable .btn-primary').click()
-        self.wait_until_present('#allbuildstable tbody tr')
+        self.wait_until_visible('#allbuildstable tbody tr')
         self.assertTrue(self.find('#completed_on_filter').get_attribute(
             'class').find('btn-primary') != -1)
         # Check if filter is applied, number of builds displayed should be 6
-        time.sleep(1)
-        self.assertTrue(len(self.find_all('#allbuildstable tbody tr')) == 6)
+        self.assertTrue(len(self.find_all('#allbuildstable tbody tr')) >= 4)
 
     def test_builds_table_editColumn(self):
         """ Test the edit column feature in the builds table on the all builds page """
@@ -414,7 +425,7 @@
                 )
         url = reverse('all-builds')
         self.get(url)
-        self.wait_until_present('#allbuildstable tbody tr')
+        self.wait_until_visible('#allbuildstable tbody tr')
 
         # Check edit column
         edit_column = self.find('#edit-columns-button')
@@ -439,15 +450,14 @@
         def test_show_rows(row_to_show, show_row_link):
             # Check that we can show rows == row_to_show
             show_row_link.select_by_value(str(row_to_show))
-            self.wait_until_present('#allbuildstable tbody tr')
-            time.sleep(1)
+            self.wait_until_visible('#allbuildstable tbody tr', poll=2)
             self.assertTrue(
                 len(self.find_all('#allbuildstable tbody tr')) == row_to_show
             )
 
         url = reverse('all-builds')
         self.get(url)
-        self.wait_until_present('#allbuildstable tbody tr')
+        self.wait_until_visible('#allbuildstable tbody tr')
 
         show_rows = self.driver.find_elements(
             By.XPATH,
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_all_projects_page.py b/poky/bitbake/lib/toaster/tests/browser/test_all_projects_page.py
index a880dbc..6540dfa 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_all_projects_page.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_all_projects_page.py
@@ -7,8 +7,8 @@
 # SPDX-License-Identifier: GPL-2.0-only
 #
 
+import os
 import re
-import time
 
 from django.urls import reverse
 from django.utils import timezone
@@ -20,6 +20,7 @@
 
 from selenium.webdriver.common.by import By
 
+
 class TestAllProjectsPage(SeleniumTestCase):
     """ Browser tests for projects page /projects/ """
 
@@ -29,7 +30,8 @@
 
     def setUp(self):
         """ Add default project manually """
-        project = Project.objects.create_project(self.CLI_BUILDS_PROJECT_NAME, None)
+        project = Project.objects.create_project(
+            self.CLI_BUILDS_PROJECT_NAME, None)
         self.default_project = project
         self.default_project.is_default = True
         self.default_project.save()
@@ -60,12 +62,14 @@
 
     def _add_non_default_project(self):
         """ Add another project """
-        bbv = BitbakeVersion.objects.create(name='test bbv', giturl='/tmp/',
+        builldir = os.environ.get('BUILDDIR', './')
+        bbv = BitbakeVersion.objects.create(name='test bbv', giturl=f'{builldir}/',
                                             branch='master', dirpath='')
         self.release = Release.objects.create(name='test release',
                                               branch_name='master',
                                               bitbake_version=bbv)
-        self.project = Project.objects.create_project(self.PROJECT_NAME, self.release)
+        self.project = Project.objects.create_project(
+            self.PROJECT_NAME, self.release)
         self.project.is_default = False
         self.project.save()
 
@@ -77,7 +81,7 @@
 
     def _get_row_for_project(self, project_name):
         """ Get the HTML row for a project, or None if not found """
-        self.wait_until_present('#projectstable tbody tr')
+        self.wait_until_visible('#projectstable tbody tr', poll=3)
         rows = self.find_all('#projectstable tbody tr')
 
         # find the row with a project name matching the one supplied
@@ -108,7 +112,8 @@
         url = reverse('all-projects')
         self.get(url)
 
-        default_project_row = self._get_row_for_project(self.default_project.name)
+        default_project_row = self._get_row_for_project(
+            self.default_project.name)
 
         self.assertNotEqual(default_project_row, None,
                             'default project "cli builds" should be in page')
@@ -128,7 +133,8 @@
         self.wait_until_visible("#projectstable tr")
 
         # find the row for the default project
-        default_project_row = self._get_row_for_project(self.default_project.name)
+        default_project_row = self._get_row_for_project(
+            self.default_project.name)
 
         # check the release text for the default project
         selector = 'span[data-project-field="release"] span.text-muted'
@@ -163,7 +169,8 @@
         self.wait_until_visible("#projectstable tr")
 
         # find the row for the default project
-        default_project_row = self._get_row_for_project(self.default_project.name)
+        default_project_row = self._get_row_for_project(
+            self.default_project.name)
 
         # check the machine cell for the default project
         selector = 'span[data-project-field="machine"] span.text-muted'
@@ -198,13 +205,15 @@
         self.get(reverse('all-projects'))
 
         # find the row for the default project
-        default_project_row = self._get_row_for_project(self.default_project.name)
+        default_project_row = self._get_row_for_project(
+            self.default_project.name)
 
         # check the link on the name field
         selector = 'span[data-project-field="name"] a'
         element = default_project_row.find_element(By.CSS_SELECTOR, selector)
         link_url = element.get_attribute('href').strip()
-        expected_url = reverse('projectbuilds', args=(self.default_project.id,))
+        expected_url = reverse(
+            'projectbuilds', args=(self.default_project.id,))
         msg = 'link on default project name should point to builds but was %s' % link_url
         self.assertTrue(link_url.endswith(expected_url), msg)
 
@@ -227,7 +236,7 @@
         self.get(url)
 
         # Chseck search box is present and works
-        self.wait_until_present('#projectstable tbody tr')
+        self.wait_until_visible('#projectstable tbody tr', poll=3)
         search_box = self.find('#search-input-projectstable')
         self.assertTrue(search_box.is_displayed())
 
@@ -235,8 +244,7 @@
         search_box.send_keys('test project 10')
         search_btn = self.find('#search-submit-projectstable')
         search_btn.click()
-        self.wait_until_present('#projectstable tbody tr')
-        time.sleep(1)
+        self.wait_until_visible('#projectstable tbody tr', poll=3)
         rows = self.find_all('#projectstable tbody tr')
         self.assertTrue(len(rows) == 1)
 
@@ -282,7 +290,7 @@
                 )
         url = reverse('all-projects')
         self.get(url)
-        self.wait_until_present('#projectstable tbody tr')
+        self.wait_until_visible('#projectstable tbody tr', poll=3)
 
         # Check edit column
         edit_column = self.find('#edit-columns-button')
@@ -305,19 +313,14 @@
         def test_show_rows(row_to_show, show_row_link):
             # Check that we can show rows == row_to_show
             show_row_link.select_by_value(str(row_to_show))
-            self.wait_until_present('#projectstable tbody tr')
-            sleep_time = 1
-            if row_to_show == 150:
-                # wait more time for 150 rows
-                sleep_time = 2
-            time.sleep(sleep_time)
+            self.wait_until_visible('#projectstable tbody tr', poll=3)
             self.assertTrue(
                 len(self.find_all('#projectstable tbody tr')) == row_to_show
             )
 
         url = reverse('all-projects')
         self.get(url)
-        self.wait_until_present('#projectstable tbody tr')
+        self.wait_until_visible('#projectstable tbody tr', poll=3)
 
         show_rows = self.driver.find_elements(
             By.XPATH,
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py b/poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py
index 1afa4a4..b713f30 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py
@@ -7,6 +7,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 #
 
+import os
 from django.urls import reverse
 from django.utils import timezone
 
@@ -21,7 +22,8 @@
     """ Tests for the build dashboard /build/X """
 
     def setUp(self):
-        bbv = BitbakeVersion.objects.create(name='bbv1', giturl='/tmp/',
+        builldir = os.environ.get('BUILDDIR', './')
+        bbv = BitbakeVersion.objects.create(name='bbv1', giturl=f'{builldir}/',
                                             branch='master', dirpath="")
         release = Release.objects.create(name='release1',
                                          bitbake_version=bbv)
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page_artifacts.py b/poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page_artifacts.py
index c6226d6..675825b 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page_artifacts.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page_artifacts.py
@@ -7,6 +7,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 #
 
+import os
 from django.urls import reverse
 from django.utils import timezone
 
@@ -20,7 +21,8 @@
     """ Tests for artifacts on the build dashboard /build/X """
 
     def setUp(self):
-        bbv = BitbakeVersion.objects.create(name='bbv1', giturl='/tmp/',
+        builldir = os.environ.get('BUILDDIR', './')
+        bbv = BitbakeVersion.objects.create(name='bbv1', giturl=f'{builldir}/',
                                             branch='master', dirpath="")
         release = Release.objects.create(name='release1',
                                          bitbake_version=bbv)
@@ -197,12 +199,12 @@
         # check package count and size, link on target name
         selector = '[data-value="target-package-count"]'
         element = self.find(selector)
-        self.assertEquals(element.text, '1',
+        self.assertEqual(element.text, '1',
             'package count should be shown for image builds')
 
         selector = '[data-value="target-package-size"]'
         element = self.find(selector)
-        self.assertEquals(element.text, '1.0 KB',
+        self.assertEqual(element.text, '1.0 KB',
             'package size should be shown for image builds')
 
         selector = '[data-link="target-packages"]'
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_landing_page.py b/poky/bitbake/lib/toaster/tests/browser/test_landing_page.py
index 7ec52a4..8fe5fea 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_landing_page.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_landing_page.py
@@ -14,6 +14,7 @@
 
 from orm.models import Layer, Layer_Version, Project, Build
 
+
 class TestLandingPage(SeleniumTestCase):
     """ Tests for redirects on the landing page """
 
@@ -40,7 +41,7 @@
 
         # check that the info sign is clickable
         # and info modal is appearing when clicking on the info sign
-        info_sign.click() # click on the info sign make attribute 'aria-describedby' visible
+        info_sign.click()  # click on the info sign make attribute 'aria-describedby' visible
         info_model_id = info_sign.get_attribute('aria-describedby')
         info_modal = self.find(f'#{info_model_id}')
         self.assertTrue(info_modal.is_displayed())
@@ -55,7 +56,7 @@
         self.assertTrue(documentation_link.is_displayed())
 
         # check browser open new tab toaster manual when clicking on the documentation link
-        self.assertEqual(documentation_link.get_attribute('target') , '_blank')
+        self.assertEqual(documentation_link.get_attribute('target'), '_blank')
         self.assertEqual(
             documentation_link.get_attribute('href'),
             'http://docs.yoctoproject.org/toaster-manual/index.html#toaster-user-manual')
@@ -81,7 +82,8 @@
         bitbake = jumbotron.find_element(By.LINK_TEXT, 'BitBake')
         self.assertTrue(bitbake.is_displayed())
         bitbake.click()
-        self.assertTrue("docs.yoctoproject.org/bitbake.html" in self.driver.current_url)
+        self.assertTrue(
+            "docs.yoctoproject.org/bitbake.html" in self.driver.current_url)
 
     def test_yoctoproject_jumbotron_link_visible_and_clickable(self):
         """ Test Yocto Project link jumbotron is visible and clickable: """
@@ -103,11 +105,12 @@
 
         # check Big magenta button
         big_magenta_button = jumbotron.find_element(By.LINK_TEXT,
-            'Toaster is ready to capture your command line builds'
-        )
+                                                    'Toaster is ready to capture your command line builds'
+                                                    )
         self.assertTrue(big_magenta_button.is_displayed())
         big_magenta_button.click()
-        self.assertTrue("docs.yoctoproject.org/toaster-manual/setup-and-use.html#setting-up-and-using-toaster" in self.driver.current_url)
+        self.assertTrue(
+            "docs.yoctoproject.org/toaster-manual/setup-and-use.html#setting-up-and-using-toaster" in self.driver.current_url)
 
     def test_link_create_new_project_in_jumbotron_visible_and_clickable(self):
         """ Test big blue button create new project jumbotron if visible and clickable """
@@ -120,8 +123,8 @@
 
         # check Big Blue button
         big_blue_button = jumbotron.find_element(By.LINK_TEXT,
-            'Create your first Toaster project to run manage builds'
-        )
+                                                 'Create your first Toaster project to run manage builds'
+                                                 )
         self.assertTrue(big_blue_button.is_displayed())
         big_blue_button.click()
         self.assertTrue("toastergui/newproject/" in self.driver.current_url)
@@ -132,10 +135,12 @@
         jumbotron = self.find('.jumbotron')
 
         # check Read the Toaster manual
-        toaster_manual = jumbotron.find_element(By.LINK_TEXT, 'Read the Toaster manual')
+        toaster_manual = jumbotron.find_element(
+            By.LINK_TEXT, 'Read the Toaster manual')
         self.assertTrue(toaster_manual.is_displayed())
         toaster_manual.click()
-        self.assertTrue("https://docs.yoctoproject.org/toaster-manual/index.html#toaster-user-manual" in self.driver.current_url)
+        self.assertTrue(
+            "https://docs.yoctoproject.org/toaster-manual/index.html#toaster-user-manual" in self.driver.current_url)
 
     def test_contrib_to_toaster_link_visible_and_clickable(self):
         """ Test Contribute to Toaster link jumbotron is visible and clickable: """
@@ -143,10 +148,12 @@
         jumbotron = self.find('.jumbotron')
 
         # check Contribute to Toaster
-        contribute_to_toaster = jumbotron.find_element(By.LINK_TEXT, 'Contribute to Toaster')
+        contribute_to_toaster = jumbotron.find_element(
+            By.LINK_TEXT, 'Contribute to Toaster')
         self.assertTrue(contribute_to_toaster.is_displayed())
         contribute_to_toaster.click()
-        self.assertTrue("wiki.yoctoproject.org/wiki/contribute_to_toaster" in str(self.driver.current_url).lower())
+        self.assertTrue(
+            "wiki.yoctoproject.org/wiki/contribute_to_toaster" in str(self.driver.current_url).lower())
 
     def test_only_default_project(self):
         """
@@ -206,10 +213,9 @@
 
         self.get(reverse('landing'))
 
+        self.wait_until_visible("#latest-builds", poll=3)
         elements = self.find_all('#allbuildstable')
         self.assertEqual(len(elements), 1, 'should redirect to builds')
         content = self.get_page_source()
         self.assertTrue(self.PROJECT_NAME in content,
                         'should show builds for project %s' % self.PROJECT_NAME)
-        self.assertFalse(self.CLI_BUILDS_PROJECT_NAME in content,
-                         'should not show builds for cli project')
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py b/poky/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py
index cb7b915..9deef67 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py
@@ -8,6 +8,7 @@
 #
 
 from django.urls import reverse
+from selenium.common.exceptions import ElementClickInterceptedException, TimeoutException
 from tests.browser.selenium_helpers import SeleniumTestCase
 
 from orm.models import Layer, Layer_Version, Project, LayerSource, Release
@@ -68,6 +69,7 @@
         check that the new values exist"""
 
         self.get(self.url)
+        self.wait_until_visible("#add-remove-layer-btn")
 
         self.click("#add-remove-layer-btn")
         self.click("#edit-layer-source")
@@ -105,7 +107,18 @@
         for save_btn in self.find_all(".change-btn"):
             save_btn.click()
 
-        self.click("#save-changes-for-switch")
+        try:
+            self.wait_until_visible("#save-changes-for-switch", poll=3)
+            btn_save_chg_for_switch = self.wait_until_clickable(
+                "#save-changes-for-switch", poll=3)
+            btn_save_chg_for_switch.click()
+        except ElementClickInterceptedException:
+            self.skipTest(
+                "save-changes-for-switch click intercepted. Element not visible or maybe covered by another element.")
+        except TimeoutException:
+            self.skipTest(
+                "save-changes-for-switch is not clickable within the specified timeout.")
+
         self.wait_until_visible("#edit-layer-source")
 
         # Refresh the page to see if the new values are returned
@@ -134,7 +147,18 @@
         new_dir = "/home/test/my-meta-dir"
         dir_input.send_keys(new_dir)
 
-        self.click("#save-changes-for-switch")
+        try:
+            self.wait_until_visible("#save-changes-for-switch", poll=3)
+            btn_save_chg_for_switch = self.wait_until_clickable(
+                "#save-changes-for-switch", poll=3)
+            btn_save_chg_for_switch.click()
+        except ElementClickInterceptedException:
+            self.skipTest(
+                "save-changes-for-switch click intercepted. Element not properly visible or maybe behind another element.")
+        except TimeoutException:
+            self.skipTest(
+                "save-changes-for-switch is not clickable within the specified timeout.")
+
         self.wait_until_visible("#edit-layer-source")
 
         # Refresh the page to see if the new values are returned
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py b/poky/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py
index 949a947..d7a4c34 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py
@@ -6,7 +6,6 @@
 #
 # Copyright (C) 2013-2016 Intel Corporation
 #
-import time
 from django.urls import reverse
 from django.utils import timezone
 from tests.browser.selenium_helpers import SeleniumTestCase
@@ -47,7 +46,7 @@
         # build queued; check shown as queued
         selector = base_selector + '[data-build-state="Queued"]'
         element = self.wait_until_visible(selector)
-        self.assertRegexpMatches(element.get_attribute('innerHTML'),
+        self.assertRegex(element.get_attribute('innerHTML'),
             'Build queued', 'build should show queued status')
 
         # waiting for recipes to be parsed
@@ -97,7 +96,7 @@
 
         selector = base_selector + '[data-build-state="Starting"]'
         element = self.wait_until_visible(selector)
-        self.assertRegexpMatches(element.get_attribute('innerHTML'),
+        self.assertRegex(element.get_attribute('innerHTML'),
             'Tasks starting', 'build should show "tasks starting" status')
 
         # first task finished; check tasks progress bar
@@ -186,7 +185,7 @@
         selector = '[data-latest-build-result="%s"] ' \
             '[data-build-state="Cancelling"]' % build.id
         element = self.wait_until_visible(selector)
-        self.assertRegexpMatches(element.get_attribute('innerHTML'),
+        self.assertRegex(element.get_attribute('innerHTML'),
             'Cancelling the build', 'build should show "cancelling" status')
 
         # check cancelled state
@@ -198,5 +197,5 @@
         selector = '[data-latest-build-result="%s"] ' \
             '[data-build-state="Cancelled"]' % build.id
         element = self.wait_until_visible(selector)
-        self.assertRegexpMatches(element.get_attribute('innerHTML'),
+        self.assertRegex(element.get_attribute('innerHTML'),
             'Build cancelled', 'build should show "cancelled" status')
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py b/poky/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py
index 34d1bd4..4ad22c7 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py
@@ -45,10 +45,11 @@
         )
 
         # add a fake image recipe to the layer that can be customised
+        builldir = os.environ.get('BUILDDIR', './')
         self.recipe = Recipe.objects.create(
             name='core-image-minimal',
             layer_version=layer_version,
-            file_path='/tmp/core-image-minimal.bb',
+            file_path=f'{builldir}/core-image-minimal.bb',
             is_image=True
         )
         # create a tmp file for the recipe
@@ -136,7 +137,7 @@
         """
         self._create_custom_image(self.recipe.name)
         element = self.wait_until_visible('#invalid-name-help')
-        self.assertRegexpMatches(element.text.strip(),
+        self.assertRegex(element.text.strip(),
                                  'image with this name already exists')
 
     def test_new_duplicates_project_image(self):
@@ -154,4 +155,4 @@
         self._create_custom_image(custom_image_name)
         element = self.wait_until_visible('#invalid-name-help')
         expected = 'An image with this name already exists in this project'
-        self.assertRegexpMatches(element.text.strip(), expected)
+        self.assertRegex(element.text.strip(), expected)
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_new_project_page.py b/poky/bitbake/lib/toaster/tests/browser/test_new_project_page.py
index f4b2708..0c33c44 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_new_project_page.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_new_project_page.py
@@ -6,8 +6,6 @@
 #
 # SPDX-License-Identifier: GPL-2.0-only
 #
-import time
-
 from django.urls import reverse
 from tests.browser.selenium_helpers import SeleniumTestCase
 from selenium.webdriver.support.ui import Select
@@ -54,13 +52,12 @@
         select = Select(self.find('#projectversion'))
         select.select_by_value(str(self.release.pk))
 
-        time.sleep(1)
         self.click("#create-project-button")
-        time.sleep(2)
 
         # We should get redirected to the new project's page with the
         # notification at the top
-        element = self.wait_until_visible('#project-created-notification')
+        element = self.wait_until_visible(
+            '#project-created-notification', poll=3)
 
         self.assertTrue(project_name in element.text,
                         "New project name not in new project notification")
@@ -91,9 +88,8 @@
         radio.click()
 
         self.click("#create-project-button")
-        time.sleep(2)
 
-        element = self.wait_until_visible('#hint-error-project-name')
+        element = self.wait_until_visible('#hint-error-project-name', poll=3)
 
         self.assertTrue(("Project names must be unique" in element.text),
                         "Did not find unique project name error message")
@@ -105,7 +101,6 @@
         except InvalidElementStateException:
             pass
 
-        time.sleep(2)
         self.assertTrue(
             (Project.objects.filter(name=project_name).count() == 1),
             "New project not found in database")
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_project_builds_page.py b/poky/bitbake/lib/toaster/tests/browser/test_project_builds_page.py
index 51717e7..0dba33b 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_project_builds_page.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_project_builds_page.py
@@ -7,6 +7,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 #
 
+import os
 import re
 
 from django.urls import reverse
@@ -22,7 +23,8 @@
     CLI_BUILDS_PROJECT_NAME = 'command line builds'
 
     def setUp(self):
-        bbv = BitbakeVersion.objects.create(name='bbv1', giturl='/tmp/',
+        builldir = os.environ.get('BUILDDIR', './')
+        bbv = BitbakeVersion.objects.create(name='bbv1', giturl=f'{builldir}/',
                                             branch='master', dirpath='')
         release = Release.objects.create(name='release1',
                                          bitbake_version=bbv)
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_project_config_page.py b/poky/bitbake/lib/toaster/tests/browser/test_project_config_page.py
index 7b21460..b9de541 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_project_config_page.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_project_config_page.py
@@ -7,6 +7,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 #
 
+import os
 from django.urls import reverse
 from tests.browser.selenium_helpers import SeleniumTestCase
 
@@ -22,7 +23,8 @@
         'any of these characters'
 
     def setUp(self):
-        bbv = BitbakeVersion.objects.create(name='bbv1', giturl='/tmp/',
+        builldir = os.environ.get('BUILDDIR', './')
+        bbv = BitbakeVersion.objects.create(name='bbv1', giturl=f'{builldir}/',
                                             branch='master', dirpath='')
         release = Release.objects.create(name='release1',
                                          bitbake_version=bbv)
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_sample.py b/poky/bitbake/lib/toaster/tests/browser/test_sample.py
index 7397377..f04f1d9 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_sample.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_sample.py
@@ -32,6 +32,7 @@
         """ Test that a message is shown when there are no builds """
         url = reverse('all-builds')
         self.get(url)
+        self.wait_until_visible('#empty-state-allbuildstable')  # wait for the empty state div to appear
         div_msg = self.find('#empty-state-allbuildstable .alert-info')
 
         msg = 'Sorry - no data found'
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_toastertable_ui.py b/poky/bitbake/lib/toaster/tests/browser/test_toastertable_ui.py
index e00c30a..691aca1 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_toastertable_ui.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_toastertable_ui.py
@@ -8,6 +8,7 @@
 #
 
 from datetime import datetime
+import os
 
 from django.urls import reverse
 from django.utils import timezone
@@ -59,7 +60,8 @@
         later = now + timezone.timedelta(hours=1)
         even_later = later + timezone.timedelta(hours=1)
 
-        bbv = BitbakeVersion.objects.create(name='test bbv', giturl='/tmp/',
+        builldir = os.environ.get('BUILDDIR', './')
+        bbv = BitbakeVersion.objects.create(name='test bbv', giturl=f'{builldir}/',
                                             branch='master', dirpath='')
         release = Release.objects.create(name='test release',
                                          branch_name='master',
diff --git a/poky/bitbake/lib/toaster/tests/builds/buildtest.py b/poky/bitbake/lib/toaster/tests/builds/buildtest.py
index 53cd7a9..cacfccd 100644
--- a/poky/bitbake/lib/toaster/tests/builds/buildtest.py
+++ b/poky/bitbake/lib/toaster/tests/builds/buildtest.py
@@ -88,7 +88,7 @@
 class BuildTest(unittest.TestCase):
 
     PROJECT_NAME = "Testbuild"
-    BUILDDIR = "/tmp/build/"
+    BUILDDIR = os.environ.get("BUILDDIR")
 
     def build(self, target):
         # So that the buildinfo helper uses the test database'
@@ -116,7 +116,7 @@
         project = Project.objects.create_project(name=BuildTest.PROJECT_NAME,
                                                  release=release)
 
-        passthrough_variable_names = ["SSTATE_DIR", "DL_DIR"]
+        passthrough_variable_names = ["SSTATE_DIR", "DL_DIR", "SSTATE_MIRRORS", "BB_HASHSERVE", "BB_HASHSERVE_UPSTREAM"]
         for variable_name in passthrough_variable_names:
             current_variable = os.environ.get(variable_name)
             if current_variable:
@@ -128,7 +128,7 @@
         if os.environ.get("TOASTER_TEST_USE_SSTATE_MIRROR"):
             ProjectVariable.objects.get_or_create(
                 name="SSTATE_MIRRORS",
-                value="file://.* http://sstate.yoctoproject.org/PATH;downloadfilename=PATH",
+                value="file://.* http://cdn.jsdelivr.net/yocto/sstate/all/PATH;downloadfilename=PATH",
                 project=project)
 
         ProjectTarget.objects.create(project=project,
diff --git a/poky/bitbake/lib/toaster/tests/builds/test_core_image_min.py b/poky/bitbake/lib/toaster/tests/builds/test_core_image_min.py
index 9cdaa15..c5bfdbf 100644
--- a/poky/bitbake/lib/toaster/tests/builds/test_core_image_min.py
+++ b/poky/bitbake/lib/toaster/tests/builds/test_core_image_min.py
@@ -10,6 +10,7 @@
 # Ionut Chisanovici, Paul Eggleton and Cristian Iorga
 
 import os
+import pytest
 
 from django.db.models import Q
 
@@ -20,13 +21,13 @@
 
 from tests.builds.buildtest import BuildTest
 
-
+@pytest.mark.order(4)
+@pytest.mark.django_db(True)
 class BuildCoreImageMinimal(BuildTest):
     """Build core-image-minimal and test the results"""
 
     def setUp(self):
-        self.completed_build = self.build("core-image-minimal")
-        self.built = self.target_already_built("core-image-minimal")
+        self.completed_build = self.target_already_built("core-image-minimal")
 
     # Check if build name is unique - tc_id=795
     def test_Build_Unique_Name(self):
@@ -45,17 +46,6 @@
                          total_builds,
                          msg='Build cooker log path is not unique')
 
-    # Check if task order is unique for one build - tc=824
-    def test_Task_Unique_Order(self):
-        total_task_order = Task.objects.filter(
-            build=self.built).values('order').count()
-        distinct_task_order = Task.objects.filter(
-            build=self.completed_build).values('order').distinct().count()
-
-        self.assertEqual(total_task_order,
-                         distinct_task_order,
-                         msg='Errors task order is not unique')
-
     # Check task order sequence for one build - tc=825
     def test_Task_Order_Sequence(self):
         cnt_err = []
@@ -99,7 +89,6 @@
                                                         'task_name',
                                                         'sstate_result')
         cnt_err = []
-
         for task in tasks:
             if (task['sstate_result'] != Task.SSTATE_NA and
                     task['sstate_result'] != Task.SSTATE_MISS):
@@ -222,6 +211,7 @@
     # orm_build.outcome=0 then if the file exists and its size matches
     # the file_size value. Need to add the tc in the test run
     def test_Target_File_Name_Populated(self):
+        cnt_err = []
         builds = Build.objects.filter(outcome=0).values('id')
         for build in builds:
             targets = Target.objects.filter(
@@ -231,7 +221,6 @@
                     target_id=target['id']).values('id',
                                                    'file_name',
                                                    'file_size')
-                cnt_err = []
                 for file_info in target_files:
                     target_id = file_info['id']
                     target_file_name = file_info['file_name']
diff --git a/poky/bitbake/lib/toaster/tests/commands/test_loaddata.py b/poky/bitbake/lib/toaster/tests/commands/test_loaddata.py
index 9e8d555..7d04f03 100644
--- a/poky/bitbake/lib/toaster/tests/commands/test_loaddata.py
+++ b/poky/bitbake/lib/toaster/tests/commands/test_loaddata.py
@@ -6,13 +6,13 @@
 #
 # SPDX-License-Identifier: GPL-2.0-only
 #
-
+import pytest
 from django.test import TestCase
 from django.core import management
 
 from orm.models import Layer_Version, Layer, Release, ToasterSetting
 
-
+@pytest.mark.order(2)
 class TestLoadDataFixtures(TestCase):
     """ Test loading our 3 provided fixtures """
     def test_run_loaddata_poky_command(self):
diff --git a/poky/bitbake/lib/toaster/tests/commands/test_lsupdates.py b/poky/bitbake/lib/toaster/tests/commands/test_lsupdates.py
index 3c4fbe0..30c6eeb 100644
--- a/poky/bitbake/lib/toaster/tests/commands/test_lsupdates.py
+++ b/poky/bitbake/lib/toaster/tests/commands/test_lsupdates.py
@@ -7,12 +7,13 @@
 # SPDX-License-Identifier: GPL-2.0-only
 #
 
+import pytest
 from django.test import TestCase
 from django.core import management
 
 from orm.models import Layer_Version, Machine, Recipe
 
-
+@pytest.mark.order(3)
 class TestLayerIndexUpdater(TestCase):
     def test_run_lsupdates_command(self):
         # Load some release information for us to fetch from the layer index
diff --git a/poky/bitbake/lib/toaster/tests/commands/test_runbuilds.py b/poky/bitbake/lib/toaster/tests/commands/test_runbuilds.py
index c77d6cf..849c227 100644
--- a/poky/bitbake/lib/toaster/tests/commands/test_runbuilds.py
+++ b/poky/bitbake/lib/toaster/tests/commands/test_runbuilds.py
@@ -19,6 +19,8 @@
 import subprocess
 import signal
 
+import logging
+
 
 class KillRunbuilds(threading.Thread):
     """ Kill the runbuilds process after an amount of time """
@@ -34,9 +36,12 @@
         pidfile_path = os.path.join(os.environ.get("BUILDDIR", "."),
                                     ".runbuilds.pid")
 
-        with open(pidfile_path) as pidfile:
-            pid = pidfile.read()
-            os.kill(int(pid), signal.SIGTERM)
+        try:
+            with open(pidfile_path) as pidfile:
+                pid = pidfile.read()
+                os.kill(int(pid), signal.SIGTERM)
+        except ProcessLookupError:
+            logging.warning("Runbuilds not running or already killed")
 
 
 class TestCommands(TestCase):
diff --git a/poky/bitbake/lib/toaster/tests/db/test_db.py b/poky/bitbake/lib/toaster/tests/db/test_db.py
index 0410422..072ab94 100644
--- a/poky/bitbake/lib/toaster/tests/db/test_db.py
+++ b/poky/bitbake/lib/toaster/tests/db/test_db.py
@@ -23,6 +23,7 @@
 # SOFTWARE.
 
 import sys
+import pytest
 
 try:
     from StringIO import StringIO
@@ -47,7 +48,7 @@
 def makemigrations():
     management.call_command('makemigrations')
 
-
+@pytest.mark.order(1)
 class MigrationTest(TestCase):
 
     def testPendingMigration(self):
diff --git a/poky/bitbake/lib/toaster/tests/functional/functional_helpers.py b/poky/bitbake/lib/toaster/tests/functional/functional_helpers.py
index b80d403..7c20437 100644
--- a/poky/bitbake/lib/toaster/tests/functional/functional_helpers.py
+++ b/poky/bitbake/lib/toaster/tests/functional/functional_helpers.py
@@ -11,7 +11,6 @@
 import logging
 import subprocess
 import signal
-import time
 import re
 
 from tests.browser.selenium_helpers_base import SeleniumTestCaseBase
@@ -19,26 +18,48 @@
 from selenium.common.exceptions import NoSuchElementException
 
 logger = logging.getLogger("toaster")
+toaster_processes = []
 
 class SeleniumFunctionalTestCase(SeleniumTestCaseBase):
-    wait_toaster_time = 5
+    wait_toaster_time = 10
 
     @classmethod
     def setUpClass(cls):
         # So that the buildinfo helper uses the test database'
         if os.environ.get('DJANGO_SETTINGS_MODULE', '') != \
             'toastermain.settings_test':
-            raise RuntimeError("Please initialise django with the tests settings:  " \
+            raise RuntimeError("Please initialise django with the tests settings:  "
                 "DJANGO_SETTINGS_MODULE='toastermain.settings_test'")
 
+        # Wait for any known toaster processes to exit
+        global toaster_processes
+        for toaster_process in toaster_processes:
+            try:
+                os.waitpid(toaster_process, os.WNOHANG)
+            except ChildProcessError:
+                pass
+
         # start toaster
         cmd = "bash -c 'source toaster start'"
-        p = subprocess.Popen(
+        start_process = subprocess.Popen(
             cmd,
             cwd=os.environ.get("BUILDDIR"),
             shell=True)
-        if p.wait() != 0:
-            raise RuntimeError("Can't initialize toaster")
+        toaster_processes = [start_process.pid]
+        if start_process.wait() != 0:
+            port_use = os.popen("lsof -i -P -n | grep '8000 (LISTEN)'").read().strip()
+            message = ''
+            if port_use:
+                process_id = port_use.split()[1]
+                process = os.popen(f"ps -o cmd= -p {process_id}").read().strip()
+                message = f"Port 8000 occupied by {process}"
+            raise RuntimeError(f"Can't initialize toaster. {message}")
+
+        builddir = os.environ.get("BUILDDIR")
+        with open(os.path.join(builddir, '.toastermain.pid'), 'r') as f:
+            toaster_processes.append(int(f.read()))
+        with open(os.path.join(builddir, '.runbuilds.pid'), 'r') as f:
+            toaster_processes.append(int(f.read()))
 
         super(SeleniumFunctionalTestCase, cls).setUpClass()
         cls.live_server_url = 'http://localhost:8000/'
@@ -47,22 +68,30 @@
     def tearDownClass(cls):
         super(SeleniumFunctionalTestCase, cls).tearDownClass()
 
-        # XXX: source toaster stop gets blocked, to review why?
-        # from now send SIGTERM by hand
-        time.sleep(cls.wait_toaster_time)
-        builddir = os.environ.get("BUILDDIR")
+        global toaster_processes
 
-        with open(os.path.join(builddir, '.toastermain.pid'), 'r') as f:
-            toastermain_pid = int(f.read())
-            os.kill(toastermain_pid, signal.SIGTERM)
-        with open(os.path.join(builddir, '.runbuilds.pid'), 'r') as f:
-            runbuilds_pid = int(f.read())
-            os.kill(runbuilds_pid, signal.SIGTERM)
+        cmd = "bash -c 'source toaster stop'"
+        stop_process = subprocess.Popen(
+            cmd,
+            cwd=os.environ.get("BUILDDIR"),
+            shell=True)
+        # Toaster stop has been known to hang in these tests so force kill if it stalls
+        try:
+            if stop_process.wait(cls.wait_toaster_time) != 0:
+                raise Exception('Toaster stop process failed')
+        except Exception as e:
+            if e is subprocess.TimeoutExpired:
+                print('Toaster stop process took too long. Force killing toaster...')
+            else:
+                print('Toaster stop process failed. Force killing toaster...')
+            stop_process.kill()
+            for toaster_process in toaster_processes:
+                os.kill(toaster_process, signal.SIGTERM)
 
 
     def get_URL(self):
          rc=self.get_page_source()
-         project_url=re.search("(projectPageUrl\s:\s\")(.*)(\",)",rc)
+         project_url=re.search(r"(projectPageUrl\s:\s\")(.*)(\",)",rc)
          return project_url.group(2)
 
 
diff --git a/poky/bitbake/lib/toaster/tests/functional/test_create_new_project.py b/poky/bitbake/lib/toaster/tests/functional/test_create_new_project.py
index dc7d1fc..9f88010 100644
--- a/poky/bitbake/lib/toaster/tests/functional/test_create_new_project.py
+++ b/poky/bitbake/lib/toaster/tests/functional/test_create_new_project.py
@@ -16,6 +16,7 @@
 
 
 @pytest.mark.django_db
+@pytest.mark.order("last")
 class TestCreateNewProject(SeleniumFunctionalTestCase):
 
     def _create_test_new_project(
@@ -48,7 +49,7 @@
 
         self.driver.find_element(By.ID, "create-project-button").click()
 
-        element = self.wait_until_visible('#project-created-notification')
+        element = self.wait_until_visible('#project-created-notification', poll=3)
         self.assertTrue(
             self.element_exists('#project-created-notification'),
             f"Project:{project_name} creation notification not shown"
diff --git a/poky/bitbake/lib/toaster/tests/functional/test_functional_basic.py b/poky/bitbake/lib/toaster/tests/functional/test_functional_basic.py
index f558cce..e4070fb 100644
--- a/poky/bitbake/lib/toaster/tests/functional/test_functional_basic.py
+++ b/poky/bitbake/lib/toaster/tests/functional/test_functional_basic.py
@@ -7,97 +7,111 @@
 # SPDX-License-Identifier: GPL-2.0-only
 #
 
-import re, time
+import re
 from django.urls import reverse
 import pytest
 from tests.functional.functional_helpers import SeleniumFunctionalTestCase
 from orm.models import Project
 from selenium.webdriver.common.by import By
 
+from tests.functional.utils import get_projectId_from_url
 
-@pytest.mark.order("last")
+
+@pytest.mark.django_db
+@pytest.mark.order("second_to_last")
 class FuntionalTestBasic(SeleniumFunctionalTestCase):
+    """Basic functional tests for Toaster"""
+    project_id = None
+
+    def setUp(self):
+        super(FuntionalTestBasic, self).setUp()
+        if not FuntionalTestBasic.project_id:
+            self._create_slenium_project()
+            current_url = self.driver.current_url
+            FuntionalTestBasic.project_id = get_projectId_from_url(current_url)
 
 #   testcase (1514)
-    @pytest.mark.django_db
-    def test_create_slenium_project(self):
+    def _create_slenium_project(self):
         project_name = 'selenium-project'
         self.get(reverse('newproject'))
+        self.wait_until_visible('#new-project-name', poll=3)
         self.driver.find_element(By.ID, "new-project-name").send_keys(project_name)
         self.driver.find_element(By.ID, 'projectversion').click()
         self.driver.find_element(By.ID, "create-project-button").click()
-        time.sleep(2)
-        element = self.wait_until_visible('#project-created-notification')
+        element = self.wait_until_visible('#project-created-notification', poll=10)
         self.assertTrue(self.element_exists('#project-created-notification'),'Project creation notification not shown')
         self.assertTrue(project_name in element.text,
                         "New project name not in new project notification")
         self.assertTrue(Project.objects.filter(name=project_name).count(),
                         "New project not found in database")
+        return Project.objects.last().id
 
  #  testcase (1515)
     def test_verify_left_bar_menu(self):
         self.get(reverse('all-projects'))
-        self.wait_until_visible('#projectstable')
+        self.wait_until_present('#projectstable', poll=10)
         self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
-        time.sleep(2)
+        self.wait_until_present('#config-nav', poll=10)
         self.assertTrue(self.element_exists('#config-nav'),'Configuration Tab does not exist')
         project_URL=self.get_URL()
         self.driver.find_element(By.XPATH, '//a[@href="'+project_URL+'"]').click()
-        time.sleep(2)
+        self.wait_until_present('#config-nav', poll=10)
 
         try:
             self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'customimages/"'+"]").click()
-            time.sleep(2)
+            self.wait_until_present('#config-nav', poll=10)
             self.assertTrue(re.search("Custom images",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'Custom images information is not loading properly')
         except:
             self.fail(msg='No Custom images tab available')
 
         try:
             self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'images/"'+"]").click()
+            self.wait_until_present('#config-nav', poll=10)
             self.assertTrue(re.search("Compatible image recipes",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Compatible image recipes information is not loading properly')
         except:
             self.fail(msg='No Compatible image tab available')
 
         try:
             self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'softwarerecipes/"'+"]").click()
+            self.wait_until_present('#config-nav', poll=10)
             self.assertTrue(re.search("Compatible software recipes",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Compatible software recipe information is not loading properly')
         except:
             self.fail(msg='No Compatible software recipe tab available')
 
         try:
             self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'machines/"'+"]").click()
+            self.wait_until_present('#config-nav', poll=10)
             self.assertTrue(re.search("Compatible machines",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Compatible machine information is not loading properly')
         except:
             self.fail(msg='No Compatible machines tab available')
 
         try:
             self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'layers/"'+"]").click()
+            self.wait_until_present('#config-nav', poll=10)
             self.assertTrue(re.search("Compatible layers",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Compatible layer information is not loading properly')
         except:
             self.fail(msg='No Compatible layers tab available')
 
         try:
             self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'configuration"'+"]").click()
+            self.wait_until_present('#config-nav', poll=10)
             self.assertTrue(re.search("Bitbake variables",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Bitbake variables information is not loading properly')
         except:
             self.fail(msg='No Bitbake variables tab available')
 
 #   testcase (1516)
     def test_review_configuration_information(self):
-        self.get('')
-        self.driver.find_element(By.XPATH, "//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
-        time.sleep(2)
-        self.wait_until_visible('#projectstable')
+        self.get(reverse('all-projects'))
+        self.wait_until_present('#projectstable', poll=10)
         self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
         project_URL=self.get_URL()
-        time.sleep(2)
+        self.wait_until_present('#config-nav', poll=10)
         try:
            self.assertTrue(self.element_exists('#machine-section'),'Machine section for the project configuration page does not exist')
-           self.assertTrue(re.search("qemux86",self.driver.find_element(By.XPATH, "//span[@id='project-machine-name']").text),'The machine type is not assigned')
+           self.assertTrue(re.search("qemux86-64",self.driver.find_element(By.XPATH, "//span[@id='project-machine-name']").text),'The machine type is not assigned')
            self.driver.find_element(By.XPATH, "//span[@id='change-machine-toggle']").click()
-           time.sleep(2)
-           self.wait_until_visible('#select-machine-form')
-           self.wait_until_visible('#cancel-machine-change')
+           self.wait_until_visible('#select-machine-form', poll=10)
+           self.wait_until_visible('#cancel-machine-change', poll=10)
            self.driver.find_element(By.XPATH, "//form[@id='select-machine-form']/a[@id='cancel-machine-change']").click()
         except:
            self.fail(msg='The machine information is wrong in the configuration page')
@@ -131,49 +145,42 @@
 
 #   testcase (1517)
     def test_verify_machine_information(self):
-        self.get('')
-        self.driver.find_element(By.XPATH, "//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
-        time.sleep(2)
-        self.wait_until_visible('#projectstable')
+        self.get(reverse('all-projects'))
+        self.wait_until_present('#projectstable', poll=10)
         self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
-        time.sleep(2)
+        self.wait_until_present('#config-nav', poll=10)
 
         try:
             self.assertTrue(self.element_exists('#machine-section'),'Machine section for the project configuration page does not exist')
-            self.assertTrue(re.search("qemux86",self.driver.find_element(By.ID, "project-machine-name").text),'The machine type is not assigned')
+            self.assertTrue(re.search("qemux86-64",self.driver.find_element(By.ID, "project-machine-name").text),'The machine type is not assigned')
             self.driver.find_element(By.ID, "change-machine-toggle").click()
-            time.sleep(2)
-            self.wait_until_visible('#select-machine-form')
-            self.wait_until_visible('#cancel-machine-change')
+            self.wait_until_visible('#select-machine-form', poll=10)
+            self.wait_until_visible('#cancel-machine-change', poll=10)
             self.driver.find_element(By.ID, "cancel-machine-change").click()
         except:
             self.fail(msg='The machine information is wrong in the configuration page')
 
 #   testcase (1518)
     def test_verify_most_built_recipes_information(self):
-        self.get('')
-        self.driver.find_element(By.XPATH, "//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
-        time.sleep(2)
-        self.wait_until_visible('#projectstable')
+        self.get(reverse('all-projects'))
+        self.wait_until_present('#projectstable', poll=10)
         self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
+        self.wait_until_present('#config-nav', poll=10)
         project_URL=self.get_URL()
-        time.sleep(2)
         try:
             self.assertTrue(re.search("You haven't built any recipes yet",self.driver.find_element(By.ID, "no-most-built").text),'Default message of no builds is not present')
             self.driver.find_element(By.XPATH, "//div[@id='no-most-built']/p/a[@href="+'"'+project_URL+'images/"'+"]").click()
-            time.sleep(2)
+            self.wait_until_present('#config-nav', poll=10)
             self.assertTrue(re.search("Compatible image recipes",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Choose a recipe to build link  is not working  properly')
         except:
             self.fail(msg='No Most built information in project detail page')
 
 #   testcase (1519)
     def test_verify_project_release_information(self):
-        self.get('')
-        self.driver.find_element(By.XPATH, "//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
-        time.sleep(2)
-        self.wait_until_visible('#projectstable')
+        self.get(reverse('all-projects'))
+        self.wait_until_present('#projectstable', poll=10)
         self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
-        time.sleep(2)
+        self.wait_until_present('#config-nav', poll=10)
 
         try:
             self.assertTrue(re.search("Yocto Project master",self.driver.find_element(By.ID, "project-release-title").text),'The project release is not defined')
@@ -182,12 +189,11 @@
 
 #   testcase (1520)
     def test_verify_layer_information(self):
-        self.get('')
-        self.driver.find_element(By.XPATH, "//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
-        self.wait_until_visible('#projectstable')
+        self.get(reverse('all-projects'))
+        self.wait_until_present('#projectstable', poll=10)
         self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
+        self.wait_until_present('#config-nav', poll=10)
         project_URL=self.get_URL()
-        time.sleep(2)
         try:
            self.driver.find_element(By.XPATH, "//div[@id='layer-container']")
            self.assertTrue(re.search("3",self.driver.find_element(By.ID, "project-layers-count").text),'There should be 3 layers listed in the layer count')
@@ -213,18 +219,18 @@
 
 #   testcase (1521)
     def test_verify_project_detail_links(self):
-        self.get('')
-        self.driver.find_element(By.XPATH, "//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
-        time.sleep(2)
-        self.wait_until_visible('#projectstable')
+        self.get(reverse('all-projects'))
+        self.wait_until_present('#projectstable', poll=10)
         self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
+        self.wait_until_present('#config-nav', poll=10)
         project_URL=self.get_URL()
-        time.sleep(2)
         self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li[@id='topbar-configuration-tab']/a[@href="+'"'+project_URL+'"'+"]").click()
+        self.wait_until_present('#config-nav', poll=10)
         self.assertTrue(re.search("Configuration",self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li[@id='topbar-configuration-tab']/a[@href="+'"'+project_URL+'"'+"]").text), 'Configuration tab in project topbar is misspelled')
 
         try:
             self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'builds/"'+"]").click()
+            self.wait_until_visible('#project-topbar', poll=10)
             self.assertTrue(re.search("Builds",self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'builds/"'+"]").text), 'Builds tab in project topbar is misspelled')
             self.driver.find_element(By.XPATH, "//div[@id='empty-state-projectbuildstable']")
         except:
@@ -232,6 +238,7 @@
 
         try:
             self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'importlayer"'+"]").click()
+            self.wait_until_visible('#project-topbar', poll=10)
             self.assertTrue(re.search("Import layer",self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'importlayer"'+"]").text), 'Import layer tab in project topbar is misspelled')
             self.driver.find_element(By.XPATH, "//fieldset[@id='repo-select']")
             self.driver.find_element(By.XPATH, "//fieldset[@id='git-repo']")
@@ -240,6 +247,7 @@
 
         try:
             self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'newcustomimage/"'+"]").click()
+            self.wait_until_visible('#project-topbar', poll=10)
             self.assertTrue(re.search("New custom image",self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'newcustomimage/"'+"]").text), 'New custom image tab in project topbar is misspelled')
             self.assertTrue(re.search("Select the image recipe you want to customise",self.driver.find_element(By.XPATH, "//div[@class='col-md-12']/h2").text),'The new custom image tab is not loading correctly')
         except:
diff --git a/poky/bitbake/lib/toaster/tests/functional/test_project_config.py b/poky/bitbake/lib/toaster/tests/functional/test_project_config.py
new file mode 100644
index 0000000..dbee36a
--- /dev/null
+++ b/poky/bitbake/lib/toaster/tests/functional/test_project_config.py
@@ -0,0 +1,341 @@
+#! /usr/bin/env python3 #
+# BitBake Toaster UI tests implementation
+#
+# Copyright (C) 2023 Savoir-faire Linux
+#
+# SPDX-License-Identifier: GPL-2.0-only
+#
+
+import string
+import random
+import pytest
+from django.urls import reverse
+from selenium.webdriver import Keys
+from selenium.webdriver.support.select import Select
+from selenium.common.exceptions import TimeoutException
+from tests.functional.functional_helpers import SeleniumFunctionalTestCase
+from selenium.webdriver.common.by import By
+
+from .utils import get_projectId_from_url
+
+
+@pytest.mark.django_db
+@pytest.mark.order("last")
+class TestProjectConfig(SeleniumFunctionalTestCase):
+    project_id = None
+    PROJECT_NAME = 'TestProjectConfig'
+    INVALID_PATH_START_TEXT = 'The directory path should either start with a /'
+    INVALID_PATH_CHAR_TEXT = 'The directory path cannot include spaces or ' \
+        'any of these characters'
+
+    def _create_project(self, project_name):
+        """ Create/Test new project using:
+          - Project Name: Any string
+          - Release: Any string
+          - Merge Toaster settings: True or False
+        """
+        self.get(reverse('newproject'))
+        self.wait_until_visible('#new-project-name', poll=2)
+        self.find("#new-project-name").send_keys(project_name)
+        select = Select(self.find("#projectversion"))
+        select.select_by_value('3')
+
+        # check merge toaster settings
+        checkbox = self.find('.checkbox-mergeattr')
+        if not checkbox.is_selected():
+            checkbox.click()
+
+        if self.PROJECT_NAME != 'TestProjectConfig':
+            # Reset project name if it's not the default one
+            self.PROJECT_NAME = 'TestProjectConfig'
+
+        self.find("#create-project-button").click()
+
+        try:
+            self.wait_until_visible('#hint-error-project-name', poll=2)
+            url = reverse('project', args=(TestProjectConfig.project_id, ))
+            self.get(url)
+            self.wait_until_visible('#config-nav', poll=3)
+        except TimeoutException:
+            self.wait_until_visible('#config-nav', poll=3)
+
+    def _random_string(self, length):
+        return ''.join(
+            random.choice(string.ascii_letters) for _ in range(length)
+        )
+
+    def _get_config_nav_item(self, index):
+        config_nav = self.find('#config-nav')
+        return config_nav.find_elements(By.TAG_NAME, 'li')[index]
+
+    def _navigate_bbv_page(self):
+        """ Navigate to project BitBake variables page """
+        # check if the menu is displayed
+        if TestProjectConfig.project_id is None:
+            self._create_project(project_name=self._random_string(10))
+            current_url = self.driver.current_url
+            TestProjectConfig.project_id = get_projectId_from_url(current_url)
+        else:
+            url = reverse('projectconf', args=(TestProjectConfig.project_id,))
+            self.get(url)
+        self.wait_until_visible('#config-nav', poll=3)
+        bbv_page_link = self._get_config_nav_item(9)
+        bbv_page_link.click()
+        self.wait_until_visible('#config-nav', poll=3)
+
+    def test_no_underscore_iamgefs_type(self):
+        """
+        Should not accept IMAGEFS_TYPE with an underscore
+        """
+        self._navigate_bbv_page()
+        imagefs_type = "foo_bar"
+
+        self.wait_until_visible('#change-image_fstypes-icon', poll=2)
+
+        self.click('#change-image_fstypes-icon')
+
+        self.enter_text('#new-imagefs_types', imagefs_type)
+
+        element = self.wait_until_visible('#hintError-image-fs_type', poll=2)
+
+        self.assertTrue(("A valid image type cannot include underscores" in element.text),
+                        "Did not find underscore error message")
+
+    def test_checkbox_verification(self):
+        """
+        Should automatically check the checkbox if user enters value
+        text box, if value is there in the checkbox.
+        """
+        self._navigate_bbv_page()
+
+        imagefs_type = "btrfs"
+
+        self.wait_until_visible('#change-image_fstypes-icon', poll=2)
+
+        self.click('#change-image_fstypes-icon')
+
+        self.enter_text('#new-imagefs_types', imagefs_type)
+
+        checkboxes = self.driver.find_elements(By.XPATH, "//input[@class='fs-checkbox-fstypes']")
+
+        for checkbox in checkboxes:
+            if checkbox.get_attribute("value") == "btrfs":
+               self.assertEqual(checkbox.is_selected(), True)
+
+    def test_textbox_with_checkbox_verification(self):
+        """
+        Should automatically add or remove value in textbox, if user checks
+        or unchecks checkboxes.
+        """
+        self._navigate_bbv_page()
+
+        self.wait_until_visible('#change-image_fstypes-icon', poll=2)
+
+        self.click('#change-image_fstypes-icon')
+
+        checkboxes_selector = '.fs-checkbox-fstypes'
+
+        self.wait_until_visible(checkboxes_selector, poll=2)
+        checkboxes = self.find_all(checkboxes_selector)
+
+        for checkbox in checkboxes:
+            if checkbox.get_attribute("value") == "cpio":
+               checkbox.click()
+               element = self.driver.find_element(By.ID, 'new-imagefs_types')
+
+               self.wait_until_visible('#new-imagefs_types', poll=2)
+
+               self.assertTrue(("cpio" in element.get_attribute('value'),
+                               "Imagefs not added into the textbox"))
+               checkbox.click()
+               self.assertTrue(("cpio" not in element.text),
+                               "Image still present in the textbox")
+
+    def test_set_download_dir(self):
+        """
+        Validate the allowed and disallowed types in the directory field for
+        DL_DIR
+        """
+        self._navigate_bbv_page()
+
+        # activate the input to edit download dir
+        try:
+            change_dl_dir_btn = self.wait_until_visible('#change-dl_dir-icon', poll=2)
+        except TimeoutException:
+            # If download dir is not displayed, test is skipped
+            change_dl_dir_btn = None
+
+        if change_dl_dir_btn:
+            change_dl_dir_btn = self.wait_until_visible('#change-dl_dir-icon', poll=2)
+            change_dl_dir_btn.click()
+
+            # downloads dir path doesn't start with / or ${...}
+            input_field = self.wait_until_visible('#new-dl_dir', poll=2)
+            input_field.clear()
+            self.enter_text('#new-dl_dir', 'home/foo')
+            element = self.wait_until_visible('#hintError-initialChar-dl_dir', poll=2)
+
+            msg = 'downloads directory path starts with invalid character but ' \
+                'treated as valid'
+            self.assertTrue((self.INVALID_PATH_START_TEXT in element.text), msg)
+
+            # downloads dir path has a space
+            self.driver.find_element(By.ID, 'new-dl_dir').clear()
+            self.enter_text('#new-dl_dir', '/foo/bar a')
+
+            element = self.wait_until_visible('#hintError-dl_dir', poll=2)
+            msg = 'downloads directory path characters invalid but treated as valid'
+            self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
+
+            # downloads dir path starts with ${...} but has a space
+            self.driver.find_element(By.ID,'new-dl_dir').clear()
+            self.enter_text('#new-dl_dir', '${TOPDIR}/down foo')
+
+            element = self.wait_until_visible('#hintError-dl_dir', poll=2)
+            msg = 'downloads directory path characters invalid but treated as valid'
+            self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
+
+            # downloads dir path starts with /
+            self.driver.find_element(By.ID,'new-dl_dir').clear()
+            self.enter_text('#new-dl_dir', '/bar/foo')
+
+            hidden_element = self.driver.find_element(By.ID,'hintError-dl_dir')
+            self.assertEqual(hidden_element.is_displayed(), False,
+                'downloads directory path valid but treated as invalid')
+
+            # downloads dir path starts with ${...}
+            self.driver.find_element(By.ID,'new-dl_dir').clear()
+            self.enter_text('#new-dl_dir', '${TOPDIR}/down')
+
+            hidden_element = self.driver.find_element(By.ID,'hintError-dl_dir')
+            self.assertEqual(hidden_element.is_displayed(), False,
+                'downloads directory path valid but treated as invalid')
+
+    def test_set_sstate_dir(self):
+        """
+        Validate the allowed and disallowed types in the directory field for
+        SSTATE_DIR
+        """
+        self._navigate_bbv_page()
+
+        try:
+            btn_chg_sstate_dir = self.wait_until_visible(
+                '#change-sstate_dir-icon',
+                poll=2
+            )
+            self.click('#change-sstate_dir-icon')
+        except TimeoutException:
+            # If sstate_dir is not displayed, test is skipped
+            btn_chg_sstate_dir = None
+
+        if btn_chg_sstate_dir:  # Skip continuation if sstate_dir is not displayed
+            # path doesn't start with / or ${...}
+            input_field = self.wait_until_visible('#new-sstate_dir', poll=2)
+            input_field.clear()
+            self.enter_text('#new-sstate_dir', 'home/foo')
+            element = self.wait_until_visible('#hintError-initialChar-sstate_dir', poll=2)
+
+            msg = 'sstate directory path starts with invalid character but ' \
+                'treated as valid'
+            self.assertTrue((self.INVALID_PATH_START_TEXT in element.text), msg)
+
+            # path has a space
+            self.driver.find_element(By.ID, 'new-sstate_dir').clear()
+            self.enter_text('#new-sstate_dir', '/foo/bar a')
+
+            element = self.wait_until_visible('#hintError-sstate_dir', poll=2)
+            msg = 'sstate directory path characters invalid but treated as valid'
+            self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
+
+            # path starts with ${...} but has a space
+            self.driver.find_element(By.ID,'new-sstate_dir').clear()
+            self.enter_text('#new-sstate_dir', '${TOPDIR}/down foo')
+
+            element = self.wait_until_visible('#hintError-sstate_dir', poll=2)
+            msg = 'sstate directory path characters invalid but treated as valid'
+            self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
+
+            # path starts with /
+            self.driver.find_element(By.ID,'new-sstate_dir').clear()
+            self.enter_text('#new-sstate_dir', '/bar/foo')
+
+            hidden_element = self.driver.find_element(By.ID, 'hintError-sstate_dir')
+            self.assertEqual(hidden_element.is_displayed(), False,
+                'sstate directory path valid but treated as invalid')
+
+            # paths starts with ${...}
+            self.driver.find_element(By.ID, 'new-sstate_dir').clear()
+            self.enter_text('#new-sstate_dir', '${TOPDIR}/down')
+
+            hidden_element = self.driver.find_element(By.ID, 'hintError-sstate_dir')
+            self.assertEqual(hidden_element.is_displayed(), False,
+                'sstate directory path valid but treated as invalid')
+
+    def _change_bbv_value(self, **kwargs):
+        var_name, field, btn_id, input_id, value, save_btn, *_ = kwargs.values()
+        """ Change bitbake variable value """
+        self._navigate_bbv_page()
+        self.wait_until_visible(f'#{btn_id}', poll=2)
+        if kwargs.get('new_variable'):
+            self.find(f"#{btn_id}").clear()
+            self.enter_text(f"#{btn_id}", f"{var_name}")
+        else:
+            self.click(f'#{btn_id}')
+            self.wait_until_visible(f'#{input_id}', poll=2)
+
+        if kwargs.get('is_select'):
+            select = Select(self.find(f'#{input_id}'))
+            select.select_by_visible_text(value)
+        else:
+            self.find(f"#{input_id}").clear()
+            self.enter_text(f'#{input_id}', f'{value}')
+        self.click(f'#{save_btn}')
+        value_displayed = str(self.wait_until_visible(f'#{field}').text).lower()
+        msg = f'{var_name} variable not changed'
+        self.assertTrue(str(value).lower() in value_displayed, msg)
+
+    def test_change_distro_var(self):
+        """ Test changing distro variable """
+        self._change_bbv_value(
+            var_name='DISTRO',
+            field='distro',
+            btn_id='change-distro-icon',
+            input_id='new-distro',
+            value='poky-changed',
+            save_btn="apply-change-distro",
+        )
+
+    def test_set_image_install_append_var(self):
+        """ Test setting IMAGE_INSTALL:append variable """
+        self._change_bbv_value(
+            var_name='IMAGE_INSTALL:append',
+            field='image_install',
+            btn_id='change-image_install-icon',
+            input_id='new-image_install',
+            value='bash, apt, busybox',
+            save_btn="apply-change-image_install",
+        )
+
+    def test_set_package_classes_var(self):
+        """ Test setting PACKAGE_CLASSES variable """
+        self._change_bbv_value(
+            var_name='PACKAGE_CLASSES',
+            field='package_classes',
+            btn_id='change-package_classes-icon',
+            input_id='package_classes-select',
+            value='package_deb',
+            save_btn="apply-change-package_classes",
+            is_select=True,
+        )
+
+    def test_create_new_bbv(self):
+        """ Test creating new bitbake variable """
+        self._change_bbv_value(
+            var_name='New_Custom_Variable',
+            field='configvar-list',
+            btn_id='variable',
+            input_id='value',
+            value='new variable value',
+            save_btn="add-configvar-button",
+            new_variable=True
+        )
diff --git a/poky/bitbake/lib/toaster/tests/functional/test_project_page.py b/poky/bitbake/lib/toaster/tests/functional/test_project_page.py
index 03f64f8..31177cc 100644
--- a/poky/bitbake/lib/toaster/tests/functional/test_project_page.py
+++ b/poky/bitbake/lib/toaster/tests/functional/test_project_page.py
@@ -6,88 +6,89 @@
 # SPDX-License-Identifier: GPL-2.0-only
 #
 
+import os
 import random
 import string
+from unittest import skip
 import pytest
-from time import sleep
 from django.urls import reverse
 from django.utils import timezone
 from selenium.webdriver.common.keys import Keys
 from selenium.webdriver.support.select import Select
-from selenium.common.exceptions import NoSuchElementException, TimeoutException
+from selenium.common.exceptions import TimeoutException
 from tests.functional.functional_helpers import SeleniumFunctionalTestCase
 from orm.models import Build, Project, Target
 from selenium.webdriver.common.by import By
 
+from .utils import get_projectId_from_url, wait_until_build, wait_until_build_cancelled
+
 
 @pytest.mark.django_db
+@pytest.mark.order("last")
 class TestProjectPage(SeleniumFunctionalTestCase):
+    project_id = None
+    PROJECT_NAME = 'TestProjectPage'
 
-    def setUp(self):
-        super().setUp()
-        release = '3'
-        project_name = 'project_' + self.generate_random_string()
-        self._create_test_new_project(
-            project_name,
-            release,
-            False,
-        )
-
-    def generate_random_string(self, length=10):
-        characters = string.ascii_letters + string.digits  # alphabetic and numerical characters
-        random_string = ''.join(random.choice(characters) for _ in range(length))
-        return random_string
-
-    def _create_test_new_project(
-        self,
-        project_name,
-        release,
-        merge_toaster_settings,
-    ):
+    def _create_project(self, project_name):
         """ Create/Test new project using:
           - Project Name: Any string
           - Release: Any string
           - Merge Toaster settings: True or False
         """
         self.get(reverse('newproject'))
-        self.driver.find_element(By.ID,
-                                 "new-project-name").send_keys(project_name)
-
-        select = Select(self.find('#projectversion'))
-        select.select_by_value(release)
+        self.wait_until_visible('#new-project-name')
+        self.find("#new-project-name").send_keys(project_name)
+        select = Select(self.find("#projectversion"))
+        select.select_by_value('3')
 
         # check merge toaster settings
         checkbox = self.find('.checkbox-mergeattr')
-        if merge_toaster_settings:
-            if not checkbox.is_selected():
-                checkbox.click()
-        else:
-            if checkbox.is_selected():
-                checkbox.click()
+        if not checkbox.is_selected():
+            checkbox.click()
 
-        self.driver.find_element(By.ID, "create-project-button").click()
+        if self.PROJECT_NAME != 'TestProjectPage':
+            # Reset project name if it's not the default one
+            self.PROJECT_NAME = 'TestProjectPage'
+
+        self.find("#create-project-button").click()
+
+        try:
+            self.wait_until_visible('#hint-error-project-name')
+            url = reverse('project', args=(TestProjectPage.project_id, ))
+            self.get(url)
+            self.wait_until_visible('#config-nav', poll=3)
+        except TimeoutException:
+            self.wait_until_visible('#config-nav', poll=3)
+
+    def _random_string(self, length):
+        return ''.join(
+            random.choice(string.ascii_letters) for _ in range(length)
+        )
+
+    def _navigate_to_project_page(self):
+        # Navigate to project page
+        if TestProjectPage.project_id is None:
+            self._create_project(project_name=self._random_string(10))
+            current_url = self.driver.current_url
+            TestProjectPage.project_id = get_projectId_from_url(current_url)
+        else:
+            url = reverse('project', args=(TestProjectPage.project_id,))
+            self.get(url)
+        self.wait_until_visible('#config-nav')
 
     def _get_create_builds(self, **kwargs):
         """ Create a build and return the build object """
         # parameters for builds to associate with the projects
         now = timezone.now()
-        release = '3'
-        project_name = 'projectmaster'
-        self._create_test_new_project(
-            project_name+"2",
-            release,
-            False,
-        )
-
         self.project1_build_success = {
-            'project': Project.objects.get(id=1),
+            'project': Project.objects.get(id=TestProjectPage.project_id),
             'started_on': now,
             'completed_on': now,
             'outcome': Build.SUCCEEDED
         }
 
         self.project1_build_failure = {
-            'project': Project.objects.get(id=1),
+            'project': Project.objects.get(id=TestProjectPage.project_id),
             'started_on': now,
             'completed_on': now,
             'outcome': Build.FAILED
@@ -180,9 +181,7 @@
 
     def _navigate_to_config_nav(self, nav_id, nav_index):
         # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
-        self.wait_until_visible('#config-nav')
+        self._navigate_to_project_page()
         # click on "Software recipe" tab
         soft_recipe = self._get_config_nav_item(nav_index)
         soft_recipe.click()
@@ -211,29 +210,6 @@
                 if row_to_show not in to_skip:
                     test_show_rows(row_to_show, show_row_link)
 
-    def _wait_until_build(self, state):
-        timeout = 10
-        start_time = 0
-        while True:
-            if start_time > timeout:
-                raise TimeoutException(
-                    f'Build did not reach {state} state within {timeout} seconds'
-                )
-            try:
-                last_build_state = self.driver.find_element(
-                    By.XPATH,
-                    '//*[@id="latest-builds"]/div[1]//div[@class="build-state"]',
-                )
-                build_state = last_build_state.get_attribute(
-                    'data-build-state')
-                state_text = state.lower().split()
-                if any(x in str(build_state).lower() for x in state_text):
-                    break
-            except NoSuchElementException:
-                continue
-            start_time += 1
-            sleep(1) # take a breath and try again
-
     def _mixin_test_table_search_input(self, **kwargs):
         input_selector, input_text, searchBtn_selector, table_selector, *_ = kwargs.values()
         # Test search input
@@ -245,11 +221,19 @@
         rows = self.find_all(f'#{table_selector} tbody tr')
         self.assertTrue(len(rows) > 0)
 
+    def test_create_project(self):
+        """ Create/Test new project using:
+          - Project Name: Any string
+          - Release: Any string
+          - Merge Toaster settings: True or False
+        """
+        self._create_project(project_name=self.PROJECT_NAME)
+
     def test_image_recipe_editColumn(self):
         """ Test the edit column feature in image recipe table on project page """
         self._get_create_builds(success=10, failure=10)
 
-        url = reverse('projectimagerecipes', args=(1,))
+        url = reverse('projectimagerecipes', args=(TestProjectPage.project_id,))
         self.get(url)
         self.wait_until_present('#imagerecipestable tbody tr')
 
@@ -276,8 +260,7 @@
           - AT RIGHT -> button "New project", displayed, clickable
         """
         # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
+        self._navigate_to_project_page()
 
         # check page header
         # AT LEFT -> Logo of Yocto project
@@ -360,8 +343,7 @@
           - Check project name is changed
         """
         # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
+        self._navigate_to_project_page()
 
         # click on "Edit" icon button
         self.wait_until_visible('#project-name-container')
@@ -388,8 +370,7 @@
           Check search box used to build recipes
         """
         # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
+        self._navigate_to_project_page()
 
         # check "configuration" tab
         self.wait_until_visible('#topbar-configuration-tab')
@@ -397,7 +378,7 @@
         self.assertTrue(config_tab.get_attribute('class') == 'active')
         self.assertTrue('Configuration' in str(config_tab.text))
         self.assertTrue(
-            f"/toastergui/project/1" in str(self.driver.current_url)
+            f"/toastergui/project/{TestProjectPage.project_id}" in str(self.driver.current_url)
         )
 
         def get_tabs():
@@ -420,7 +401,7 @@
         check_tab_link(
             1,
             'Builds',
-            f"/toastergui/project/1/builds"
+            f"/toastergui/project/{TestProjectPage.project_id}/builds"
         )
 
         # check "Import layers" tab
@@ -429,7 +410,7 @@
         check_tab_link(
             2,
             'Import layer',
-            f"/toastergui/project/1/importlayer"
+            f"/toastergui/project/{TestProjectPage.project_id}/importlayer"
         )
 
         # check "New custom image" tab
@@ -438,7 +419,7 @@
         check_tab_link(
             3,
             'New custom image',
-            f"/toastergui/project/1/newcustomimage"
+            f"/toastergui/project/{TestProjectPage.project_id}/newcustomimage"
         )
 
         # check search box can be use to build recipes
@@ -480,12 +461,20 @@
             '//td[@class="add-del-layers"]//a[1]'
         )
         build_btn.click()
-        self._wait_until_build('parsing starting cloning queued')
+        build_state = wait_until_build(self, 'queued cloning starting parsing failed')
         lastest_builds = self.driver.find_elements(
             By.XPATH,
             '//div[@id="latest-builds"]/div'
         )
         self.assertTrue(len(lastest_builds) > 0)
+        last_build = lastest_builds[0]
+        cancel_button = last_build.find_element(
+            By.XPATH,
+            '//span[@class="cancel-build-btn pull-right alert-link"]',
+        )
+        cancel_button.click()
+        if 'starting' not in build_state:  # change build state when cancelled in starting state
+            wait_until_build_cancelled(self)
 
         # check software recipe table feature(show/hide column, pagination)
         self._navigate_to_config_nav('softwarerecipestable', 4)
@@ -505,7 +494,10 @@
         )
         self._navigate_to_config_nav('softwarerecipestable', 4)
         # check show rows(pagination)
-        self._mixin_test_table_show_rows(table_selector='softwarerecipestable')
+        self._mixin_test_table_show_rows(
+            table_selector='softwarerecipestable',
+            to_skip=[150],
+        )
 
     def test_machines_page(self):
         """ Test Machine page
@@ -547,6 +539,7 @@
             searchBtn_selector='search-submit-machinestable',
             table_selector='machinestable'
         )
+        self.wait_until_visible('#machinestable tbody tr', poll=3)
         rows = self.find_all('#machinestable tbody tr')
         machine_to_add = rows[0]
         add_btn = machine_to_add.find_element(By.XPATH, '//td[@class="add-del-layers"]')
@@ -571,7 +564,10 @@
         )
         self._navigate_to_config_nav('machinestable', 5)
         # check show rows(pagination)
-        self._mixin_test_table_show_rows(table_selector='machinestable')
+        self._mixin_test_table_show_rows(
+            table_selector='machinestable',
+            to_skip=[150],
+        )
 
     def test_layers_page(self):
         """ Test layers page
@@ -593,6 +589,7 @@
             table_selector='layerstable'
         )
         # check "Add layer" button works
+        self.wait_until_visible('#layerstable tbody tr', poll=3)
         rows = self.find_all('#layerstable tbody tr')
         layer_to_add = rows[0]
         add_btn = layer_to_add.find_element(
@@ -601,7 +598,7 @@
         )
         add_btn.click()
         # check modal is displayed
-        self.wait_until_visible('#dependencies-modal', poll=2)
+        self.wait_until_visible('#dependencies-modal', poll=3)
         list_dependencies = self.find_all('#dependencies-list li')
         # click on add-layers button
         add_layers_btn = self.driver.find_element(
@@ -615,6 +612,7 @@
             f'You have added {len(list_dependencies)+1} layers to your project: {input_text} and its dependencies' in str(change_notification.text)
         )
         # check "Remove layer" button works
+        self.wait_until_visible('#layerstable tbody tr', poll=3)
         rows = self.find_all('#layerstable tbody tr')
         layer_to_remove = rows[0]
         remove_btn = layer_to_remove.find_element(
@@ -643,7 +641,10 @@
         )
         self._navigate_to_config_nav('layerstable', 6)
         # check show rows(pagination)
-        self._mixin_test_table_show_rows(table_selector='layerstable')
+        self._mixin_test_table_show_rows(
+            table_selector='layerstable',
+            to_skip=[150],
+        )
 
     def test_distro_page(self):
         """ Test distros page
@@ -693,7 +694,7 @@
         # check show rows(pagination)
         self._mixin_test_table_show_rows(
             table_selector='distrostable',
-            to_skip=[150]
+            to_skip=[150],
         )
 
     def test_single_layer_page(self):
@@ -706,7 +707,7 @@
                 - Check layer summary
                 - Check layer description
         """
-        url = reverse("layerdetails", args=(1, 8))
+        url = reverse("layerdetails", args=(TestProjectPage.project_id, 8))
         self.get(url)
         self.wait_until_visible('.page-header')
         # check title is displayed
@@ -765,7 +766,7 @@
                 - Check recipe: name, summary, description, Version, Section,
                 License, Approx. packages included, Approx. size, Recipe file
         """
-        url = reverse("recipedetails", args=(1, 53428))
+        url = reverse("recipedetails", args=(TestProjectPage.project_id, 53428))
         self.get(url)
         self.wait_until_visible('.page-header')
         # check title is displayed
diff --git a/poky/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py b/poky/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py
index 23012d7..ee1f5c4 100644
--- a/poky/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py
+++ b/poky/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py
@@ -6,102 +6,102 @@
 # SPDX-License-Identifier: GPL-2.0-only
 #
 
-from time import sleep
+import string
+import random
 import pytest
-from django.utils import timezone
 from django.urls import reverse
 from selenium.webdriver import Keys
 from selenium.webdriver.support.select import Select
-from selenium.common.exceptions import NoSuchElementException
-from orm.models import Build, Project, Target
+from selenium.common.exceptions import NoSuchElementException, TimeoutException
+from orm.models import Project
 from tests.functional.functional_helpers import SeleniumFunctionalTestCase
 from selenium.webdriver.common.by import By
 
+from .utils import get_projectId_from_url, wait_until_build, wait_until_build_cancelled
+
 
 @pytest.mark.django_db
+@pytest.mark.order("last")
 class TestProjectConfigTab(SeleniumFunctionalTestCase):
+    PROJECT_NAME = 'TestProjectConfigTab'
+    project_id = None
 
-    def setUp(self):
-        self.recipe = None
-        super().setUp()
-        release = '3'
-        project_name = 'projectmaster'
-        self._create_test_new_project(
-            project_name,
-            release,
-            False,
-        )
-
-    def _create_test_new_project(
-        self,
-        project_name,
-        release,
-        merge_toaster_settings,
-    ):
+    def _create_project(self, project_name, **kwargs):
         """ Create/Test new project using:
           - Project Name: Any string
           - Release: Any string
           - Merge Toaster settings: True or False
         """
+        release = kwargs.get('release', '3')
         self.get(reverse('newproject'))
-        self.driver.find_element(By.ID,
-                                 "new-project-name").send_keys(project_name)
-
-        select = Select(self.find('#projectversion'))
+        self.wait_until_visible('#new-project-name')
+        self.find("#new-project-name").send_keys(project_name)
+        select = Select(self.find("#projectversion"))
         select.select_by_value(release)
 
         # check merge toaster settings
         checkbox = self.find('.checkbox-mergeattr')
-        if merge_toaster_settings:
-            if not checkbox.is_selected():
-                checkbox.click()
+        if not checkbox.is_selected():
+            checkbox.click()
+
+        if self.PROJECT_NAME != 'TestProjectConfigTab':
+            # Reset project name if it's not the default one
+            self.PROJECT_NAME = 'TestProjectConfigTab'
+
+        self.find("#create-project-button").click()
+
+        try:
+            self.wait_until_visible('#hint-error-project-name', poll=3)
+            url = reverse('project', args=(TestProjectConfigTab.project_id, ))
+            self.get(url)
+            self.wait_until_visible('#config-nav', poll=3)
+        except TimeoutException:
+            self.wait_until_visible('#config-nav', poll=3)
+
+    def _random_string(self, length):
+        return ''.join(
+            random.choice(string.ascii_letters) for _ in range(length)
+        )
+
+    def _navigate_to_project_page(self):
+        # Navigate to project page
+        if TestProjectConfigTab.project_id is None:
+            self._create_project(project_name=self._random_string(10))
+            current_url = self.driver.current_url
+            TestProjectConfigTab.project_id = get_projectId_from_url(
+                current_url)
         else:
-            if checkbox.is_selected():
-                checkbox.click()
-
-        self.driver.find_element(By.ID, "create-project-button").click()
-
-    @classmethod
-    def _wait_until_build(cls, state):
-        while True:
-            try:
-                last_build_state = cls.driver.find_element(
-                    By.XPATH,
-                    '//*[@id="latest-builds"]/div[1]//div[@class="build-state"]',
-                )
-                build_state = last_build_state.get_attribute(
-                    'data-build-state')
-                state_text = state.lower().split()
-                if any(x in str(build_state).lower() for x in state_text):
-                    break
-            except NoSuchElementException:
-                continue
-            sleep(1)
+            url = reverse('project', args=(TestProjectConfigTab.project_id,))
+            self.get(url)
+        self.wait_until_visible('#config-nav')
 
     def _create_builds(self):
         # check search box can be use to build recipes
         search_box = self.find('#build-input')
-        search_box.send_keys('core-image-minimal')
+        search_box.send_keys('foo')
         self.find('#build-button').click()
-        sleep(1)
-        self.wait_until_visible('#latest-builds')
+        self.wait_until_present('#latest-builds')
         # loop until reach the parsing state
-        self._wait_until_build('parsing starting cloning')
+        wait_until_build(self, 'queued cloning starting parsing failed')
         lastest_builds = self.driver.find_elements(
             By.XPATH,
             '//div[@id="latest-builds"]/div',
         )
         last_build = lastest_builds[0]
         self.assertTrue(
-            'core-image-minimal' in str(last_build.text)
+            'foo' in str(last_build.text)
         )
-        cancel_button = last_build.find_element(
-            By.XPATH,
-            '//span[@class="cancel-build-btn pull-right alert-link"]',
-        )
-        cancel_button.click()
-        sleep(1)
-        self._wait_until_build('cancelled')
+        last_build = lastest_builds[0]
+        try:
+            cancel_button = last_build.find_element(
+                By.XPATH,
+                '//span[@class="cancel-build-btn pull-right alert-link"]',
+            )
+            cancel_button.click()
+        except NoSuchElementException:
+            # Skip if the build is already cancelled
+            pass
+        wait_until_build_cancelled(self)
 
     def _get_tabs(self):
         # tabs links list
@@ -114,64 +114,6 @@
         config_nav = self.find('#config-nav')
         return config_nav.find_elements(By.TAG_NAME, 'li')[index]
 
-    def _get_create_builds(self, **kwargs):
-        """ Create a build and return the build object """
-        # parameters for builds to associate with the projects
-        now = timezone.now()
-        release = '3'
-        project_name = 'projectmaster'
-        self._create_test_new_project(
-            project_name+"2",
-            release,
-            False,
-        )
-
-        self.project1_build_success = {
-            'project': Project.objects.get(id=1),
-            'started_on': now,
-            'completed_on': now,
-            'outcome': Build.SUCCEEDED
-        }
-
-        self.project1_build_failure = {
-            'project': Project.objects.get(id=1),
-            'started_on': now,
-            'completed_on': now,
-            'outcome': Build.FAILED
-        }
-        build1 = Build.objects.create(**self.project1_build_success)
-        build2 = Build.objects.create(**self.project1_build_failure)
-
-        # add some targets to these builds so they have recipe links
-        # (and so we can find the row in the ToasterTable corresponding to
-        # a particular build)
-        Target.objects.create(build=build1, target='foo')
-        Target.objects.create(build=build2, target='bar')
-
-        if kwargs:
-            # Create kwargs.get('success') builds with success status with target
-            # and kwargs.get('failure') builds with failure status with target
-            for i in range(kwargs.get('success', 0)):
-                now = timezone.now()
-                self.project1_build_success['started_on'] = now
-                self.project1_build_success[
-                    'completed_on'] = now - timezone.timedelta(days=i)
-                build = Build.objects.create(**self.project1_build_success)
-                Target.objects.create(build=build,
-                                      target=f'{i}_success_recipe',
-                                      task=f'{i}_success_task')
-
-            for i in range(kwargs.get('failure', 0)):
-                now = timezone.now()
-                self.project1_build_failure['started_on'] = now
-                self.project1_build_failure[
-                    'completed_on'] = now - timezone.timedelta(days=i)
-                build = Build.objects.create(**self.project1_build_failure)
-                Target.objects.create(build=build,
-                                      target=f'{i}_fail_recipe',
-                                      task=f'{i}_fail_task')
-        return build1, build2
-
     def test_project_config_nav(self):
         """ Test project config tab navigation:
         - Check if the menu is displayed and contains the right elements:
@@ -188,12 +130,7 @@
             - Actions
             - Delete project
         """
-        # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
-
-        # check if the menu is displayed
-        self.wait_until_visible('#config-nav')
+        self._navigate_to_project_page()
 
         def _get_config_nav_item(index):
             config_nav = self.find('#config-nav')
@@ -221,14 +158,28 @@
         self.assertTrue("actions" in str(actions.text).lower())
 
         conf_nav_list = [
-            [0, 'Configuration', f"/toastergui/project/1"],  # config
-            [2, 'Custom images', f"/toastergui/project/1/customimages"],  # custom images
-            [3, 'Image recipes', f"/toastergui/project/1/images"],  # image recipes
-            [4, 'Software recipes', f"/toastergui/project/1/softwarerecipes"],  # software recipes
-            [5, 'Machines', f"/toastergui/project/1/machines"],  # machines
-            [6, 'Layers', f"/toastergui/project/1/layers"],  # layers
-            [7, 'Distro', f"/toastergui/project/1/distro"],  # distro
-            [9, 'BitBake variables', f"/toastergui/project/1/configuration"],  # bitbake variables
+            # config
+            [0, 'Configuration',
+                f"/toastergui/project/{TestProjectConfigTab.project_id}"],
+            # custom images
+            [2, 'Custom images',
+                f"/toastergui/project/{TestProjectConfigTab.project_id}/customimages"],
+            # image recipes
+            [3, 'Image recipes',
+                f"/toastergui/project/{TestProjectConfigTab.project_id}/images"],
+            # software recipes
+            [4, 'Software recipes',
+                f"/toastergui/project/{TestProjectConfigTab.project_id}/softwarerecipes"],
+            # machines
+            [5, 'Machines',
+                f"/toastergui/project/{TestProjectConfigTab.project_id}/machines"],
+            # layers
+            [6, 'Layers',
+                f"/toastergui/project/{TestProjectConfigTab.project_id}/layers"],
+            # distro
+            [7, 'Distros',
+                f"/toastergui/project/{TestProjectConfigTab.project_id}/distros"],
+            #  [9, 'BitBake variables', f"/toastergui/project/{TestProjectConfigTab.project_id}/configuration"],  # bitbake variables
         ]
         for index, item_name, url in conf_nav_list:
             item = _get_config_nav_item(index)
@@ -236,6 +187,96 @@
                 item.click()
             check_config_nav_item(index, item_name, url)
 
+    def test_image_recipe_editColumn(self):
+        """ Test the edit column feature in image recipe table on project page """
+        def test_edit_column(check_box_id):
+            # Check that we can hide/show table column
+            check_box = self.find(f'#{check_box_id}')
+            th_class = str(check_box_id).replace('checkbox-', '')
+            if check_box.is_selected():
+                # check if column is visible in table
+                self.assertTrue(
+                    self.find(
+                        f'#imagerecipestable thead th.{th_class}'
+                    ).is_displayed(),
+                    f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
+                )
+                check_box.click()
+                # check if column is hidden in table
+                self.assertFalse(
+                    self.find(
+                        f'#imagerecipestable thead th.{th_class}'
+                    ).is_displayed(),
+                    f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
+                )
+            else:
+                # check if column is hidden in table
+                self.assertFalse(
+                    self.find(
+                        f'#imagerecipestable thead th.{th_class}'
+                    ).is_displayed(),
+                    f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
+                )
+                check_box.click()
+                # check if column is visible in table
+                self.assertTrue(
+                    self.find(
+                        f'#imagerecipestable thead th.{th_class}'
+                    ).is_displayed(),
+                    f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
+                )
+
+        self._navigate_to_project_page()
+        # navigate to project image recipe page
+        recipe_image_page_link = self._get_config_nav_item(3)
+        recipe_image_page_link.click()
+        self.wait_until_present('#imagerecipestable tbody tr')
+
+        # Check edit column
+        edit_column = self.find('#edit-columns-button')
+        self.assertTrue(edit_column.is_displayed())
+        edit_column.click()
+        # Check dropdown is visible
+        self.wait_until_visible('ul.dropdown-menu.editcol')
+
+        # Check that we can hide the edit column
+        test_edit_column('checkbox-get_description_or_summary')
+        test_edit_column('checkbox-layer_version__get_vcs_reference')
+        test_edit_column('checkbox-layer_version__layer__name')
+        test_edit_column('checkbox-license')
+        test_edit_column('checkbox-recipe-file')
+        test_edit_column('checkbox-section')
+        test_edit_column('checkbox-version')
+
+    def test_image_recipe_show_rows(self):
+        """ Test the show rows feature in image recipe table on project page """
+        def test_show_rows(row_to_show, show_row_link):
+            # Check that we can show rows == row_to_show
+            show_row_link.select_by_value(str(row_to_show))
+            self.wait_until_visible('#imagerecipestable tbody tr')
+            self.assertTrue(
+                len(self.find_all('#imagerecipestable tbody tr')) == row_to_show
+            )
+
+        self._navigate_to_project_page()
+        # navigate to project image recipe page
+        recipe_image_page_link = self._get_config_nav_item(3)
+        recipe_image_page_link.click()
+        self.wait_until_present('#imagerecipestable tbody tr')
+
+        show_rows = self.driver.find_elements(
+            By.XPATH,
+            '//select[@class="form-control pagesize-imagerecipestable"]'
+        )
+        # Check show rows
+        for show_row_link in show_rows:
+            show_row_link = Select(show_row_link)
+            test_show_rows(10, show_row_link)
+            test_show_rows(25, show_row_link)
+            test_show_rows(50, show_row_link)
+            test_show_rows(100, show_row_link)
+            test_show_rows(150, show_row_link)
+
     def test_project_config_tab_right_section(self):
         """ Test project config tab right section contains five blocks:
             - Machine:
@@ -257,35 +298,31 @@
                     - meta-poky
                     - meta-yocto-bsp
         """
-        # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
-
+        # Create a new project for this test
+        project_name = self._random_string(10)
+        self._create_project(project_name=project_name)
         # check if the menu is displayed
         self.wait_until_visible('#project-page')
         block_l = self.driver.find_element(
             By.XPATH, '//*[@id="project-page"]/div[2]')
-        machine = self.find('#machine-section')
-        distro = self.find('#distro-section')
-        most_built_recipes = self.driver.find_element(
-            By.XPATH, '//*[@id="project-page"]/div[1]/div[3]')
         project_release = self.driver.find_element(
             By.XPATH, '//*[@id="project-page"]/div[1]/div[4]')
         layers = block_l.find_element(By.ID, 'layer-container')
 
-        def check_machine_distro(self, item_name, new_item_name, block):
+        def check_machine_distro(self, item_name, new_item_name, block_id):
+            block = self.find(f'#{block_id}')
             title = block.find_element(By.TAG_NAME, 'h3')
             self.assertTrue(item_name.capitalize() in title.text)
-            edit_btn = block.find_element(By.ID, f'change-{item_name}-toggle')
+            edit_btn = self.find(f'#change-{item_name}-toggle')
             edit_btn.click()
-            sleep(1)
-            name_input = block.find_element(By.ID, f'{item_name}-change-input')
+            self.wait_until_visible(f'#{item_name}-change-input')
+            name_input = self.find(f'#{item_name}-change-input')
             name_input.clear()
             name_input.send_keys(new_item_name)
-            change_btn = block.find_element(By.ID, f'{item_name}-change-btn')
+            change_btn = self.find(f'#{item_name}-change-btn')
             change_btn.click()
-            sleep(1)
-            project_name = block.find_element(By.ID, f'project-{item_name}-name')
+            self.wait_until_visible(f'#project-{item_name}-name')
+            project_name = self.find(f'#project-{item_name}-name')
             self.assertTrue(new_item_name in project_name.text)
             # check change notificaiton is displayed
             change_notification = self.find('#change-notification')
@@ -294,9 +331,9 @@
             )
 
         # Machine
-        check_machine_distro(self, 'machine', 'qemux86-64', machine)
+        check_machine_distro(self, 'machine', 'qemux86-64', 'machine-section')
         # Distro
-        check_machine_distro(self, 'distro', 'poky-altcfg', distro)
+        check_machine_distro(self, 'distro', 'poky-altcfg', 'distro-section')
 
         # Project release
         title = project_release.find_element(By.TAG_NAME, 'h3')
@@ -304,7 +341,6 @@
         self.assertTrue(
             "Yocto Project master" in self.find('#project-release-title').text
         )
-
         # Layers
         title = layers.find_element(By.TAG_NAME, 'h3')
         self.assertTrue("Layers" in title.text)
@@ -314,7 +350,9 @@
         # meta-yocto-bsp
         layers_list = layers.find_element(By.ID, 'layers-in-project-list')
         layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li')
-        self.assertTrue(len(layers_list_items) == 3)
+        # remove all layers except the first three layers
+        for i in range(3, len(layers_list_items)):
+            layers_list_items[i].find_element(By.TAG_NAME, 'span').click()
         # check can add a layer if exists
         add_layer_input = layers.find_element(By.ID, 'layer-add-input')
         add_layer_input.send_keys('meta-oe')
@@ -326,56 +364,70 @@
         dropdown_item.click()
         add_layer_btn = layers.find_element(By.ID, 'add-layer-btn')
         add_layer_btn.click()
-        sleep(1)
+        self.wait_until_visible('#layers-in-project-list')
         # check layer is added
         layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li')
         self.assertTrue(len(layers_list_items) == 4)
 
-        # Most built recipes
-        title = most_built_recipes.find_element(By.TAG_NAME, 'h3')
-        self.assertTrue("Most built recipes" in title.text)
-        # Create a new builds 5
+    def test_most_build_recipes(self):
+        """ Test most build recipes block contains"""
+        def rebuild_from_most_build_recipes(recipe_list_items):
+            checkbox = recipe_list_items[0].find_element(By.TAG_NAME, 'input')
+            checkbox.click()
+            build_btn = self.find('#freq-build-btn')
+            build_btn.click()
+            self.wait_until_visible('#latest-builds')
+            wait_until_build(self, 'queued cloning starting parsing failed')
+            lastest_builds = self.driver.find_elements(
+                By.XPATH,
+                '//div[@id="latest-builds"]/div'
+            )
+            self.assertTrue(len(lastest_builds) >= 2)
+            last_build = lastest_builds[0]
+            try:
+                cancel_button = last_build.find_element(
+                    By.XPATH,
+                    '//span[@class="cancel-build-btn pull-right alert-link"]',
+                )
+                cancel_button.click()
+            except NoSuchElementException:
+                # Skip if the build is already cancelled
+                pass
+            wait_until_build_cancelled(self)
+        # Create a new project for remaining asserts
+        project_name = self._random_string(10)
+        self._create_project(project_name=project_name, release='2')
+        current_url = self.driver.current_url
+        TestProjectConfigTab.project_id = get_projectId_from_url(current_url)
+        url = current_url.split('?')[0]
+
+        # Create a new builds
         self._create_builds()
 
-        # Refresh the page
-        self.get(url)
+        # back to project page
+        self.driver.get(url)
 
-        sleep(1)  # wait for page to load
-        self.wait_until_visible('#project-page')
-        # check can select a recipe and build it
+        self.wait_until_visible('#project-page', poll=3)
+
+        # Most built recipes
         most_built_recipes = self.driver.find_element(
             By.XPATH, '//*[@id="project-page"]/div[1]/div[3]')
-        recipe_list = most_built_recipes.find_element(By.ID, 'freq-build-list')
+        title = most_built_recipes.find_element(By.TAG_NAME, 'h3')
+        self.assertTrue("Most built recipes" in title.text)
+        # check can select a recipe and build it
+        self.wait_until_visible('#freq-build-list', poll=3)
+        recipe_list = self.find('#freq-build-list')
         recipe_list_items = recipe_list.find_elements(By.TAG_NAME, 'li')
         self.assertTrue(
             len(recipe_list_items) > 0,
-            msg="No recipes found in the most built recipes list",
+            msg="Any recipes found in the most built recipes list",
         )
-        checkbox = recipe_list_items[0].find_element(By.TAG_NAME, 'input')
-        checkbox.click()
-        build_btn = self.find('#freq-build-btn')
-        build_btn.click()
-        sleep(1)  # wait for page to load
-        self.wait_until_visible('#latest-builds')
-        self._wait_until_build('parsing starting cloning queueing')
-        lastest_builds = self.driver.find_elements(
-            By.XPATH,
-            '//div[@id="latest-builds"]/div'
-        )
-        last_build = lastest_builds[0]
-        cancel_button = last_build.find_element(
-            By.XPATH,
-            '//span[@class="cancel-build-btn pull-right alert-link"]',
-        )
-        cancel_button.click()
-        self.assertTrue(len(lastest_builds) == 2)
+        rebuild_from_most_build_recipes(recipe_list_items)
+        TestProjectConfigTab.project_id = None  # reset project id
 
     def test_project_page_tab_importlayer(self):
         """ Test project page tab import layer """
-        # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
-
+        self._navigate_to_project_page()
         # navigate to "Import layers" tab
         import_layers_tab = self._get_tabs()[2]
         import_layers_tab.find_element(By.TAG_NAME, 'a').click()
@@ -415,10 +467,10 @@
 
     def test_project_page_custom_image_no_image(self):
         """ Test project page tab "New custom image" when no custom image """
-        # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
-
+        project_name = self._random_string(10)
+        self._create_project(project_name=project_name)
+        current_url = self.driver.current_url
+        TestProjectConfigTab.project_id = get_projectId_from_url(current_url)
         # navigate to "Custom image" tab
         custom_image_section = self._get_config_nav_item(2)
         custom_image_section.click()
@@ -433,8 +485,9 @@
         div_empty_msg = self.find('#empty-state-customimagestable')
         link_create_custom_image = div_empty_msg.find_element(
             By.TAG_NAME, 'a')
+        self.assertTrue(TestProjectConfigTab.project_id is not None)
         self.assertTrue(
-            f"/toastergui/project/1/newcustomimage" in str(
+            f"/toastergui/project/{TestProjectConfigTab.project_id}/newcustomimage" in str(
                 link_create_custom_image.get_attribute('href')
             )
         )
@@ -443,6 +496,7 @@
                 link_create_custom_image.text
             )
         )
+        TestProjectConfigTab.project_id = None  # reset project id
 
     def test_project_page_image_recipe(self):
         """ Test project page section images
@@ -451,11 +505,7 @@
             - Check image recipe build button works
             - Check image recipe table features(show/hide column, pagination)
         """
-        # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
-        self.wait_until_visible('#config-nav')
-
+        self._navigate_to_project_page()
         # navigate to "Images section"
         images_section = self._get_config_nav_item(3)
         images_section.click()
@@ -471,108 +521,3 @@
         self.wait_until_visible('#imagerecipestable tbody tr')
         rows = self.find_all('#imagerecipestable tbody tr')
         self.assertTrue(len(rows) > 0)
-
-        # Test build button
-        image_to_build = rows[0]
-        build_btn = image_to_build.find_element(
-            By.XPATH,
-            '//td[@class="add-del-layers"]'
-        )
-        build_btn.click()
-        self._wait_until_build('parsing starting cloning')
-        lastest_builds = self.driver.find_elements(
-            By.XPATH,
-            '//div[@id="latest-builds"]/div'
-        )
-        self.assertTrue(len(lastest_builds) > 0)
-
-    def test_image_recipe_editColumn(self):
-        """ Test the edit column feature in image recipe table on project page """
-        self._get_create_builds(success=10, failure=10)
-
-        def test_edit_column(check_box_id):
-            # Check that we can hide/show table column
-            check_box = self.find(f'#{check_box_id}')
-            th_class = str(check_box_id).replace('checkbox-', '')
-            if check_box.is_selected():
-                # check if column is visible in table
-                self.assertTrue(
-                    self.find(
-                        f'#imagerecipestable thead th.{th_class}'
-                    ).is_displayed(),
-                    f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
-                )
-                check_box.click()
-                # check if column is hidden in table
-                self.assertFalse(
-                    self.find(
-                        f'#imagerecipestable thead th.{th_class}'
-                    ).is_displayed(),
-                    f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
-                )
-            else:
-                # check if column is hidden in table
-                self.assertFalse(
-                    self.find(
-                        f'#imagerecipestable thead th.{th_class}'
-                    ).is_displayed(),
-                    f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
-                )
-                check_box.click()
-                # check if column is visible in table
-                self.assertTrue(
-                    self.find(
-                        f'#imagerecipestable thead th.{th_class}'
-                    ).is_displayed(),
-                    f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
-                )
-
-        url = reverse('projectimagerecipes', args=(1,))
-        self.get(url)
-        self.wait_until_present('#imagerecipestable tbody tr')
-
-        # Check edit column
-        edit_column = self.find('#edit-columns-button')
-        self.assertTrue(edit_column.is_displayed())
-        edit_column.click()
-        # Check dropdown is visible
-        self.wait_until_visible('ul.dropdown-menu.editcol')
-
-        # Check that we can hide the edit column
-        test_edit_column('checkbox-get_description_or_summary')
-        test_edit_column('checkbox-layer_version__get_vcs_reference')
-        test_edit_column('checkbox-layer_version__layer__name')
-        test_edit_column('checkbox-license')
-        test_edit_column('checkbox-recipe-file')
-        test_edit_column('checkbox-section')
-        test_edit_column('checkbox-version')
-
-    def test_image_recipe_show_rows(self):
-        """ Test the show rows feature in image recipe table on project page """
-        self._get_create_builds(success=100, failure=100)
-
-        def test_show_rows(row_to_show, show_row_link):
-            # Check that we can show rows == row_to_show
-            show_row_link.select_by_value(str(row_to_show))
-            self.wait_until_present('#imagerecipestable tbody tr')
-            sleep(1)
-            self.assertTrue(
-                len(self.find_all('#imagerecipestable tbody tr')) == row_to_show
-            )
-
-        url = reverse('projectimagerecipes', args=(2,))
-        self.get(url)
-        self.wait_until_present('#imagerecipestable tbody tr')
-
-        show_rows = self.driver.find_elements(
-            By.XPATH,
-            '//select[@class="form-control pagesize-imagerecipestable"]'
-        )
-        # Check show rows
-        for show_row_link in show_rows:
-            show_row_link = Select(show_row_link)
-            test_show_rows(10, show_row_link)
-            test_show_rows(25, show_row_link)
-            test_show_rows(50, show_row_link)
-            test_show_rows(100, show_row_link)
-            test_show_rows(150, show_row_link)
diff --git a/poky/bitbake/lib/toaster/tests/functional/utils.py b/poky/bitbake/lib/toaster/tests/functional/utils.py
new file mode 100644
index 0000000..7269fa1
--- /dev/null
+++ b/poky/bitbake/lib/toaster/tests/functional/utils.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# BitBake Toaster UI tests implementation
+#
+# Copyright (C) 2023 Savoir-faire Linux
+#
+# SPDX-License-Identifier: GPL-2.0-only
+
+
+from time import sleep
+from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException, TimeoutException
+from selenium.webdriver.common.by import By
+
+from orm.models import Build
+
+
+def wait_until_build(test_instance, state):
+    timeout = 60
+    start_time = 0
+    build_state = ''
+    while True:
+        try:
+            if start_time > timeout:
+                raise TimeoutException(
+                    f'Build did not reach {state} state within {timeout} seconds'
+                )
+            last_build_state = test_instance.driver.find_element(
+                By.XPATH,
+                '//*[@id="latest-builds"]/div[1]//div[@class="build-state"]',
+            )
+            build_state = last_build_state.get_attribute(
+                'data-build-state')
+            state_text = state.lower().split()
+            if any(x in str(build_state).lower() for x in state_text):
+                return str(build_state).lower()
+            if 'failed' in str(build_state).lower():
+                break
+        except NoSuchElementException:
+            continue
+        except TimeoutException:
+            break
+        start_time += 1
+        sleep(1) # take a breath and try again
+
+def wait_until_build_cancelled(test_instance):
+    """ Cancel build take a while sometime, the method is to wait driver action
+        until build being cancelled
+    """
+    timeout = 30
+    start_time = 0
+    build = None
+    while True:
+        try:
+            if start_time > timeout:
+                raise TimeoutException(
+                    f'Build did not reach cancelled state within {timeout} seconds'
+                )
+            last_build_state = test_instance.driver.find_element(
+                By.XPATH,
+                '//*[@id="latest-builds"]/div[1]//div[@class="build-state"]',
+            )
+            build_state = last_build_state.get_attribute(
+                'data-build-state')
+            if 'failed' in str(build_state).lower():
+                break
+            if 'cancelling' in str(build_state).lower():
+                # Change build state to cancelled
+                if not build:  # get build object only once
+                    build = Build.objects.last()
+                    build.outcome = Build.CANCELLED
+                    build.save()
+            if 'cancelled' in str(build_state).lower():
+                break
+        except NoSuchElementException:
+            continue
+        except StaleElementReferenceException:
+            continue
+        except TimeoutException:
+            break
+        start_time += 1
+        sleep(1) # take a breath and try again
+
+def get_projectId_from_url(url):
+    # url = 'http://domainename.com/toastergui/project/1656/whatever
+    # or url = 'http://domainename.com/toastergui/project/1/
+    # or url = 'http://domainename.com/toastergui/project/186
+    assert '/toastergui/project/' in url, "URL is not valid"
+    url_to_list = url.split('/toastergui/project/')
+    return  int(url_to_list[1].split('/')[0])  # project_id
diff --git a/poky/bitbake/lib/toaster/tests/views/test_views.py b/poky/bitbake/lib/toaster/tests/views/test_views.py
index 349881e..e1adfcf 100644
--- a/poky/bitbake/lib/toaster/tests/views/test_views.py
+++ b/poky/bitbake/lib/toaster/tests/views/test_views.py
@@ -9,6 +9,7 @@
 
 """Test cases for Toaster GUI and ReST."""
 
+import os
 import pytest
 from django.test import TestCase
 from django.test.client import RequestFactory
@@ -34,11 +35,12 @@
 CLI_BUILDS_PROJECT_NAME = 'Command line builds'
 
 
-@pytest.mark.order(1)
+
 class ViewTests(TestCase):
     """Tests to verify view APIs."""
 
     fixtures = ['toastergui-unittest-data']
+    builldir = os.environ.get('BUILDDIR')
 
     def setUp(self):
 
@@ -46,7 +48,7 @@
 
         self.recipe1 = Recipe.objects.get(pk=2)
         # create a file and to recipe1 file_path
-        file_path = f"/tmp/{self.recipe1.name.strip().replace(' ', '-')}.bb"
+        file_path = f"{self.builldir}/{self.recipe1.name.strip().replace(' ', '-')}.bb"
         with open(file_path, 'w') as f:
             f.write('foo')
         self.recipe1.file_path = file_path
@@ -240,7 +242,7 @@
         recipe = CustomImageRecipe.objects.create(
                      name=name, project=self.project,
                      base_recipe=self.recipe1,
-                     file_path="/tmp/testing",
+                     file_path=f"{self.builldir}/testing",
                      layer_version=self.customr.layer_version)
         url = reverse('xhr_customrecipe_id', args=(recipe.id,))
         response = self.client.delete(url)
@@ -311,7 +313,7 @@
         """Download the recipe file generated for the custom image"""
 
         # Create a dummy recipe file for the custom image generation to read
-        open("/tmp/a_recipe.bb", 'a').close()
+        open(f"{self.builldir}/a_recipe.bb", 'a').close()
         response = self.client.get(reverse('customrecipedownload',
                                            args=(self.project.id,
                                                  self.customr.id)))