--	return self:waitForInstances(function(instances) return #instances >0 end)[1]

	local instances = self:waitForNInstances(1)
	if instances ~= nil and #instances > 0 then return instances[1] end
	return nil
end

function XPath:waitForInstances(condition)
	if type(condition) ~= "function" then error("arg #1 should be a function") end
	return self:waitFor(function()
		return self:getInstances()
	end, condition)
end

function XPath:waitForDisappear()
	local _, state = self:waitForInstances(function(instances) return #instances == 0 end)
	return state == true
end

function XPath:waitForNInstances(n)
	return self:waitForInstances(function(instances)
		return #instances >= n
	end)
end

local function makeInstance(className, props, children)
	local instance = Instance.new(className)
	if children then
		for _, child in ipairs(children) do
			child.Parent = instance
		end
	end
	if props then
		for k, v in pairs(props) do
			instance[k] = v
		end
	end
	return instance
end

local function test()
	local function closeTo(lhs, rhs, err)
		return math.abs(lhs-rhs) <= err
	end

	local specialChars = [[special chars !"#$%&'()*+,-./:;<=>?@[]\^_`{|}~]]
	local convertedSpecialChars = [[special chars !"#$%&'()*+\,-\./:;<\=>?@\[\]\\^_`{|}~]]

	local root
	if game.Workspace:FindFirstChild("root") == nil then
		root = makeInstance("Folder",
			{
				Name = "root",
				Parent = game.Workspace
			}, {
				makeInstance("Frame", {Name = "Frame"}, {
					makeInstance("TextButton", {
						Text = "Button1"
					}),
					makeInstance("TextLabel", {
						Text = "Label1"
					}),
					makeInstance("ImageButton", {

					})
				}),
				makeInstance("Frame", {Name = "Frame"}, {
					makeInstance("TextButton", {
						Text = "Button2"
					}),
					makeInstance("TextLabel", {
						Text = "Label2"
					})
				}),
				makeInstance("Frame", {Name = "Frame"}, {
					makeInstance("TextLabel", {Text = "Label3"}),
					makeInstance("Frame", {Name = specialChars}, {
						makeInstance("TextButton", {Name = "TextButton3"})
					}),
					makeInstance("TextLabel", {Name = "SpecialCharLabel", Text = specialChars})
				})
		})
	end

	local containerPath = XPath.new("game.Workspace.root.Frame")

	local getContainerDetail = function(root)
		return {
			textButton = XPath.new("TextButton", root),
			textLabel = XPath.new("TextLabel", root),
		}
	end

	local createSearch = function(container, relativePath, property, value)
		local rootPath = container:copy()
		local filter = {{key = "." .. relativePath:toString().."."..property, value = value}}
		rootPath:mergeFilter(rootPath:size(), filter)
		return rootPath
	end

	local rootPath = createSearch(containerPath, getContainerDetail().textButton, "Text", "Button2")
	print("createSearch:", rootPath:toString())
	local rootInstance = rootPath:waitForFirstInstance()
	assert(rootInstance)

	local containerDetail = getContainerDetail(rootInstance)

	local label2 = containerDetail.textLabel:waitForFirstInstance()
	assert(label2.Text == "Label2")

	print("relative path test")

	local path = XPath.new("game.Workspace.root.Frame[.TextButton.Text = Button2]")
	local instance = path:getFirstInstance()
	assert(instance)
	local relativePath = XPath.new("TextButton", instance)
	local instance = relativePath:getFirstInstance()
	assert(instance.Text == "Button2")

	local path = XPath.new("game.Workspace.root.Frame[.TextButton.Text = Button2, .ClassName = Frame].TextLabel")
	local instance = path:getFirstInstance()
	assert(instance.Text=="Label2")
	local newPathString = XPath.new(instance):toString()
	assert(newPathString=="game.Workspace.root.Frame.TextLabel")

	local pathStr = "game.Workspace.root.Frame[."..convertedSpecialChars..".TextButton3.Name = TextButton3].TextLabel"
	local path = XPath.new(pathStr)
	assert(path:toString()==pathStr)
	local instance = path:getFirstInstance()
	assert(instance.Text=="Label3")

	local pathStr = "game.Workspace.root.Frame[.SpecialCharLabel.Text = "..convertedSpecialChars.."].TextLabel"
	local path = XPath.new(pathStr)
	assert(path:toString()==pathStr)
	local instance = path:getFirstInstance()
	assert(instance.Text=="Label3")

	local pathStr = "game.Workspace.root.Frame."..convertedSpecialChars..".TextButton3"
	local path = XPath.new(pathStr)
	local newPathStr = path:toString()
	assert(newPathStr==pathStr)
	local instance = path:getFirstInstance()
	assert(instance.Name=="TextButton3")

	local rootPath = XPath.new("game.Workspace.root")
	local path = XPath.new("game.Workspace.root.Frame[.TextButton.Text = Button2, .ClassName = Frame].TextLabel")
	local relativePath = path:relative(rootPath)
	relativePath:clearFilter()

	print("testing getFirstInstance()")
	print(path:toString())
	local instance = path:getFirstInstance()
	assert(instance.Text == "Label2")

	print("testing wildcard *")
	path = XPath.new("game.Workspace.root.*[.TextButton.Text = Button2].TextLabel")
	instance = path:getFirstInstance()
	assert(instance.Text == "Label2")

	path = XPath.new("game.Workspace.root.*[.ImageButton.Name = *].TextLabel")
	instance = path:getFirstInstance()
	assert(instance.Text == "Label1")

	local timeBefore = nil
	print("testing timeout waitForNInstances() ")
	path = XPath.new("game.Workspace.root.Frame.TextLabel")
	timeBefore = tick()
	local instances, state = path:setWait(2):waitForNInstances(5)
	assert(closeTo(tick() - timeBefore, 2, 0.5))
	assert(state == false)

	print("testing normal waitForNInstances() ")
	timeBefore = tick()
	spawn(function()
		wait(2)
		makeInstance("Frame", {
				Name = "Frame",
				Parent = game.Workspace.root
			}, {
			makeInstance("TextButton", {
				Text = "Button3"
			}),
			makeInstance("TextLabel", {
				Text = "Label3"
			})
		})
	end)
	local instances = path:setWait(5):waitForNInstances(5)
	assert(closeTo(tick() - timeBefore, 2, 0.5))
	assert(#instances >= 5)

	print("testing  getFirstInstance() ")
	path = XPath.new("game.Workspace.root.Frame[.TextButton.Text = Button3]")
	instance = path:getFirstInstance()
	assert(instance.TextButton.Text == "Button3")

	print("testing timeout waitForDisappear() ")
	timeBefore = tick()
	local notExist = path:setWait(2):waitForDisappear()
	assert(notExist == false)
	assert(closeTo(tick() - timeBefore, 2, 0.5))

	print("testing normal waitForDisappear() ")
	timeBefore = tick()
	spawn(function()
		wait(2)
		instance:Destroy()
	end)
	local notExist = path:setWait(5):waitForDisappear()
	assert(closeTo(tick() - timeBefore, 2, 0.5))
	assert(notExist == true)

	print("test finised")
end
